diff --git a/MemoryCore/Dockerfile b/MemoryCore/Dockerfile new file mode 100644 index 0000000..5c44856 --- /dev/null +++ b/MemoryCore/Dockerfile @@ -0,0 +1,31 @@ +# syntax=docker/dockerfile:1 +# GODCALL memory-core — drop-in replacement for agentmemory/memory-core. +# Matches the upstream image contract exactly: +# WORKDIR /app, ENTRYPOINT tini, CMD node --import tsx src/gateway/server.ts +# config mounted at /data/config/tdai-gateway.yaml, data volume at /data/tdai-memory +# +# Build (from repo root): +# docker build -t godcall/memory-core:local -f MemoryCore/Dockerfile MemoryCore + +FROM node:22-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 make g++ git ca-certificates tini curl \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY package.json ./ +RUN npm install --no-audit --no-fund + +COPY . . + +ENV NODE_ENV=production \ + TDAI_GATEWAY_CONFIG=/data/config/tdai-gateway.yaml +EXPOSE 8420 + +HEALTHCHECK --interval=10s --timeout=5s --start-period=30s --retries=5 \ + CMD curl -sf http://localhost:8420/health || exit 1 + +ENTRYPOINT ["/usr/bin/tini", "--"] +CMD ["node", "--import", "tsx", "src/gateway/server.ts"] diff --git a/MemoryCore/src/core/prompts/l1-dedup.ts b/MemoryCore/src/core/prompts/l1-dedup.ts index 41e73c1..bb00af9 100644 --- a/MemoryCore/src/core/prompts/l1-dedup.ts +++ b/MemoryCore/src/core/prompts/l1-dedup.ts @@ -13,125 +13,125 @@ import type { MemoryRecord, ExtractedMemory } from "../record/l1-writer.js"; // System Prompt // ============================ -export const CONFLICT_DETECTION_SYSTEM_PROMPT = `你是记忆冲突检测器。批量比较多条【新记忆】与【统一候选记忆池】中的已有记忆,逐条决定如何处理。 +export const CONFLICT_DETECTION_SYSTEM_PROMPT = `You are a memory conflict detector. Batch-compare multiple [New Memories] against the existing memories in the [Unified Candidate Memory Pool], deciding how to handle each one. -**输出语言**:\`merged_content\` 使用与候选池中已有记忆相同的语言;JSON 字段名、枚举值、record_id、ISO 时间戳保持英文。 +**Output language**: write \`merged_content\` in the same language as the existing memories in the candidate pool; keep JSON field names, enum values, record_id, and ISO timestamps in English. -## 核心规则 +## Core rules -- **跨 type 合并**:不同 type(persona / episodic / instruction / work_fact / work_task / work_method / work_artifact)的记忆如果语义上描述同一事实/事件,**可以合并**。 -- **多对多合并**:一条新记忆可以同时替换/合并候选池中的**多条**已有记忆(通过 target_ids 数组指定)。 -- 合并后你必须判断新记忆的最佳 type(merged_type)。 +- **Cross-type merging**: memories of different types (persona / episodic / instruction / work_fact / work_task / work_method / work_artifact) **may be merged** if they semantically describe the same fact/event. +- **Many-to-many merging**: one new memory may simultaneously replace/merge **multiple** existing memories in the candidate pool (specified via the target_ids array). +- After merging, you must decide the new memory's best type (merged_type). -## 判断逻辑 +## Judgment logic -1. **分辨记忆性质**: - - **状态类**(persona/instruction):偏好、特质、长期设定、相对稳定的事实、行为规则 - - **事件类**(episodic):一次性经历、带时间点的客观记录,建议合并同一件事的前因后果 +1. **Distinguish the memory's nature**: + - **State-like** (persona/instruction): preferences, traits, long-term settings, relatively stable facts, behavior rules + - **Event-like** (episodic): one-off experiences, objective records with a point in time; prefer merging the cause and effect of the same event -2. **判断是否同一事实/事件**:主体相同、主题一致、时间接近、scene_name 相似 +2. **Decide whether it is the same fact/event**: same subject, consistent topic, close in time, similar scene_name -3. **选择动作**: - - "store":视为新信息,新增当前记忆。 - - "skip":已有记忆更好,新记忆无增量或更模糊,忽略当前记忆。 - - "update":同一事实/事件,新记忆在内容或时间上更优(更具体、更晚或纠错),以新记忆为主覆盖旧记忆,可保留旧记忆中仍正确的细节。 - - "merge":同一事实或同一演化过程,多条记忆信息互补且不矛盾,合并成一条更完整记忆,信息尽量不冗余。 +3. **Choose the action**: + - "store": treat as new information; add the current memory. + - "skip": the existing memory is better — the new memory adds nothing or is vaguer; ignore the current memory. + - "update": same fact/event, and the new memory is better in content or time (more specific, later, or a correction); overwrite the old memory with the new one as the primary source, optionally preserving still-correct details from the old memory. + - "merge": same fact or the same evolving process; multiple memories are complementary and non-contradictory — merge them into one more complete memory, keeping the information as non-redundant as possible. -4. **策略倾向**: - - 状态类:多条描述同一偏好/特质 → 倾向 merge;无增量 → skip;明确更新 → update - - 事件类:同一事件的前因后果、不同阶段 → 倾向 merge 为一条完整叙述;完全相同 → skip - - 跨类型示例:一条 episodic "用户在 2018 年开始做播客" + 一条 persona "用户有播客制作经验" → 可 merge 为一条 persona 或 episodic(取决于信息侧重) +4. **Strategy tendencies**: + - State-like: multiple descriptions of the same preference/trait → lean merge; no new information → skip; an explicit update → update + - Event-like: cause/effect or different phases of the same event → lean merge into one complete narrative; exact duplicate → skip + - Cross-type example: an episodic "The user started making a podcast in 2018" + a persona "The user has podcast-production experience" → may merge into one persona or episodic memory (depending on where the information's emphasis lies) -5. **timestamp 处理**: - - merge / update 时,merged_timestamps 应包含**所有相关记忆的时间戳并集**(去重排序) - - 这样可以保留事件发生的完整时间线 +5. **timestamp handling**: + - On merge / update, merged_timestamps should contain the **union of all related memories' timestamps** (deduplicated and sorted) + - This preserves the complete timeline of when the events occurred -## 输出格式 +## Output format -严格输出 JSON 数组,每个元素对应一条新记忆的决策。不输出任何其他内容: +Output strictly a JSON array, one element per new memory's decision. Output nothing else: [ { - "record_id": "新记忆的 record_id", + "record_id": "the new memory's record_id", "action": "store|update|skip|merge", - "target_ids": ["要删除的候选记忆 record_id 1", "record_id 2"], - "merged_content": "合并/更新后的记忆内容(merge/update 时必填)", - "merged_type": "合并后的最佳 type:persona|episodic|instruction|work_fact|work_task|work_method|work_artifact(merge/update 时必填)", + "target_ids": ["candidate memory record_id 1 to delete", "record_id 2"], + "merged_content": "the merged/updated memory content (required for merge/update)", + "merged_type": "the best type after merging: persona|episodic|instruction|work_fact|work_task|work_method|work_artifact (required for merge/update)", "merged_priority": 85, - "merged_timestamps": ["合并后的时间戳数组,包含所有新旧记忆时间戳的并集(merge/update 时必填)"] + "merged_timestamps": ["the merged timestamp array, containing the union of all old and new memory timestamps (required for merge/update)"] } ] -字段说明: -- target_ids:要删除替换的旧记忆 ID **数组**(可以 1 条或多条)。store/skip 时省略或为空。 -- merged_content:merge/update 时的最终记忆文本。store/skip 时省略。 -- merged_type:merge/update 后记忆应归属的 type。根据合并后内容本质判断。 -- merged_priority:merge/update 后的新优先级(0-100 整数,merge/update 时必填)。合并后信息更完整、更确定,通常应**酌情提升** priority(例如两条 priority 70 的记忆合并后可提升到 80)。参考标准:80-100(核心特质/重要事件),60-79(一般偏好/普通活动),<60(次要信息)。 -- merged_timestamps:合并后的时间戳数组。收集新记忆 + 所有被合并旧记忆的时间戳,去重排序。`; +Field notes: +- target_ids: the **array** of old memory IDs to delete and replace (1 or more allowed). Omit or leave empty for store/skip. +- merged_content: the final memory text for merge/update. Omit for store/skip. +- merged_type: the type the memory should belong to after merge/update. Judge from the essential nature of the merged content. +- merged_priority: the new priority after merge/update (integer 0-100, required for merge/update). Since merged information is more complete and more certain, you should usually **raise** priority as appropriate (e.g. two priority-70 memories may rise to 80 after merging). Reference scale: 80-100 (core traits / important events), 60-79 (ordinary preferences / everyday activities), <60 (minor information). +- merged_timestamps: the merged timestamp array. Collect the timestamps of the new memory + all merged-away old memories, deduplicate and sort.`; -export const WORK_CONFLICT_DETECTION_SYSTEM_PROMPT = `你是团队工作记忆冲突检测器。批量比较多条【新记忆】与【统一候选记忆池】中的已有记忆,逐条决定如何处理。 +export const WORK_CONFLICT_DETECTION_SYSTEM_PROMPT = `You are a team work-memory conflict detector. Batch-compare multiple [New Memories] against the existing memories in the [Unified Candidate Memory Pool], deciding how to handle each one. -**输出语言**:\`merged_content\` 使用与候选池中已有记忆相同的语言;JSON 字段名、枚举值、record_id、ISO 时间戳保持英文。 +**Output language**: write \`merged_content\` in the same language as the existing memories in the candidate pool; keep JSON field names, enum values, record_id, and ISO timestamps in English. -## 核心规则 +## Core rules -- **跨 type 合并**:不同 type(work_fact / work_task / work_method / work_artifact)的记忆如果语义上描述同一工作对象、任务、方法或资产,**可以合并**。 -- **多对多合并**:一条新记忆可以同时替换/合并候选池中的**多条**已有记忆(通过 target_ids 数组指定)。 -- 合并后你必须判断新记忆的最佳 type(merged_type)。 -- 记忆默认会在项目团队内共享,合并内容应只保留工作相关信息。 +- **Cross-type merging**: memories of different types (work_fact / work_task / work_method / work_artifact) **may be merged** if they semantically describe the same work object, task, method, or asset. +- **Many-to-many merging**: one new memory may simultaneously replace/merge **multiple** existing memories in the candidate pool (specified via the target_ids array). +- After merging, you must decide the new memory's best type (merged_type). +- Memories are shared within the project team by default; merged content should keep only work-related information. -## 判断逻辑 +## Judgment logic -1. **分辨记忆性质**: - - **工作事实类(work_fact)**:项目事实、需求、决策、状态、风险、约束、实验结果、客户反馈。 - - **工作任务类(work_task)**:待办、owner、deadline、下一步计划、任务状态变化。 - - **工作方法类(work_method)**:SOP、禁忌、原则、经验、设计思路、判断标准、Agent 行为规则。 - - **工作资产类(work_artifact)**:文档、PR、Issue、Prompt、报告、代码分支、设计稿、链接等。 +1. **Distinguish the memory's nature**: + - **Work-fact-like (work_fact)**: project facts, requirements, decisions, status, risks, constraints, experiment results, customer feedback. + - **Work-task-like (work_task)**: to-dos, owner, deadline, next-step plans, task status changes. + - **Work-method-like (work_method)**: SOPs, taboos, principles, experience, design rationale, judgment criteria, Agent behavior rules. + - **Work-artifact-like (work_artifact)**: documents, PRs, Issues, Prompts, reports, code branches, design drafts, links, etc. -2. **判断是否同一工作对象/演化过程**: - - 同一项目、模块、需求、任务、风险、决策、方法、资产,且 scene_name 或语义高度相似。 - - 同一任务的不同阶段、同一方法的补充、同一资产的版本或用途变化,通常可以合并。 - - 仅属于同一大项目但讨论对象不同,不应强行合并。 +2. **Decide whether it is the same work object / evolving process**: + - Same project, module, requirement, task, risk, decision, method, or asset, with a highly similar scene_name or semantics. + - Different phases of the same task, additions to the same method, or version/usage changes of the same asset can usually be merged. + - Belonging to the same large project but discussing different objects should NOT be force-merged. -3. **选择动作**: - - "store":视为新信息,新增当前记忆。 - - "skip":已有记忆更好,新记忆无增量或更模糊,忽略当前记忆。 - - "update":同一工作对象,新记忆更具体、更新、更权威或纠正旧信息,以新记忆为主覆盖旧记忆,可保留旧记忆中仍正确的细节。 - - "merge":同一工作对象或同一演化过程,新旧记忆互补且不矛盾,合并成一条更完整记忆,信息尽量不冗余。 +3. **Choose the action**: + - "store": treat as new information; add the current memory. + - "skip": the existing memory is better — the new memory adds nothing or is vaguer; ignore the current memory. + - "update": same work object, and the new memory is more specific, newer, more authoritative, or corrects the old information; overwrite the old memory with the new one as the primary source, optionally preserving still-correct details from the old memory. + - "merge": same work object or the same evolving process; old and new memories are complementary and non-contradictory — merge them into one more complete memory, keeping the information as non-redundant as possible. -4. **策略倾向**: - - work_fact:同一事实/决策/状态的补充或修正 → 倾向 update 或 merge。 - - work_task:同一任务的 owner、deadline、状态变化 → 倾向 update;补充依赖或验收标准 → 倾向 merge。 - - work_method:同一 SOP、禁忌、原则、经验的补充 → 倾向 merge;更清晰通用的表述 → 倾向 update。 - - work_artifact:同一文档、PR、Prompt、报告等资产的用途、版本、链接补充 → 倾向 merge 或 update。 - - 跨类型示例:一条 work_fact "团队决定 L1 type 保持少量高层分类" + 一条 work_method "L1 type 不宜过细,否则影响 L2/L3 聚合" → 可 merge 为 work_method。 +4. **Strategy tendencies**: + - work_fact: additions or corrections to the same fact/decision/status → lean update or merge. + - work_task: owner, deadline, or status changes of the same task → lean update; added dependencies or acceptance criteria → lean merge. + - work_method: additions to the same SOP, taboo, principle, or experience → lean merge; a clearer, more general formulation → lean update. + - work_artifact: additions of usage, version, or links for the same document, PR, Prompt, report, etc. → lean merge or update. + - Cross-type example: a work_fact "The team decided to keep L1 types as a small set of high-level categories" + a work_method "L1 types should not be too fine-grained, or L2/L3 aggregation suffers" → may merge into a work_method. -5. **timestamp 处理**: - - merge / update 时,merged_timestamps 应包含**所有相关记忆的时间戳并集**(去重排序)。 - - 这样可以保留工作事实、任务或方法演化的完整时间线。 +5. **timestamp handling**: + - On merge / update, merged_timestamps should contain the **union of all related memories' timestamps** (deduplicated and sorted). + - This preserves the complete timeline of how the work fact, task, or method evolved. -## 输出格式 +## Output format -严格输出 JSON 数组,每个元素对应一条新记忆的决策。不输出任何其他内容: +Output strictly a JSON array, one element per new memory's decision. Output nothing else: [ { - "record_id": "新记忆的 record_id", + "record_id": "the new memory's record_id", "action": "store|update|skip|merge", - "target_ids": ["要删除的候选记忆 record_id 1", "record_id 2"], - "merged_content": "合并/更新后的记忆内容(merge/update 时必填)", - "merged_type": "合并后的最佳 type:work_fact|work_task|work_method|work_artifact(merge/update 时必填)", + "target_ids": ["candidate memory record_id 1 to delete", "record_id 2"], + "merged_content": "the merged/updated memory content (required for merge/update)", + "merged_type": "the best type after merging: work_fact|work_task|work_method|work_artifact (required for merge/update)", "merged_priority": 85, - "merged_timestamps": ["合并后的时间戳数组,包含所有新旧记忆时间戳的并集(merge/update 时必填)"] + "merged_timestamps": ["the merged timestamp array, containing the union of all old and new memory timestamps (required for merge/update)"] } ] -字段说明: -- target_ids:要删除替换的旧记忆 ID **数组**(可以 1 条或多条)。store/skip 时省略或为空。 -- merged_content:merge/update 时的最终记忆文本。store/skip 时省略。 -- merged_type:merge/update 后记忆应归属的 type。根据合并后内容本质判断。 -- merged_priority:merge/update 后的新优先级(0-100 整数,merge/update 时必填)。合并后信息更完整、更确定,通常应**酌情提升** priority。参考标准:80-100(关键事实/重要任务/核心方法/重要资产),60-79(一般工作信息),<60(次要信息)。 -- merged_timestamps:合并后的时间戳数组。收集新记忆 + 所有被合并旧记忆的时间戳,去重排序。`; +Field notes: +- target_ids: the **array** of old memory IDs to delete and replace (1 or more allowed). Omit or leave empty for store/skip. +- merged_content: the final memory text for merge/update. Omit for store/skip. +- merged_type: the type the memory should belong to after merge/update. Judge from the essential nature of the merged content. +- merged_priority: the new priority after merge/update (integer 0-100, required for merge/update). Since merged information is more complete and more certain, you should usually **raise** priority as appropriate. Reference scale: 80-100 (key facts / important tasks / core methods / important assets), 60-79 (ordinary work information), <60 (minor information). +- merged_timestamps: the merged timestamp array. Collect the timestamps of the new memory + all merged-away old memories, deduplicate and sort.`; export function getConflictDetectionSystemPrompt(mode: MemoryPromptMode = "chat"): string { return mode === "code" ? WORK_CONFLICT_DETECTION_SYSTEM_PROMPT : CONFLICT_DETECTION_SYSTEM_PROMPT; @@ -188,10 +188,10 @@ export function formatBatchConflictPrompt(matches: CandidateMatch[]): string { let poolSection: string; if (poolList.length === 0) { - poolSection = "## 统一候选记忆池\n\n(空,没有已有记忆,所有新记忆直接 store)"; + poolSection = "## Unified Candidate Memory Pool\n\n(empty — no existing memories; store all new memories directly)"; } else { const poolStr = JSON.stringify(poolList, null, 2); - poolSection = `## 统一候选记忆池(共 ${poolList.length} 条已有记忆)\n\n${poolStr}`; + poolSection = `## Unified Candidate Memory Pool (${poolList.length} existing memories)\n\n${poolStr}`; } // Step 3: Format each new memory with its related candidate IDs @@ -200,7 +200,7 @@ export function formatBatchConflictPrompt(matches: CandidateMatch[]): string { const relatedNote = relatedIds.length > 0 ? JSON.stringify(relatedIds) - : "[](无相似候选,直接 store)"; + : "[] (no similar candidates — store directly)"; const memStr = JSON.stringify( { @@ -214,7 +214,7 @@ export function formatBatchConflictPrompt(matches: CandidateMatch[]): string { 2, ); - return `### 第 ${idx + 1} 条新记忆 (record_id: ${m.newMemory.record_id})\n${memStr}\n\n【关联候选 ID】${relatedNote}`; + return `### New memory ${idx + 1} (record_id: ${m.newMemory.record_id})\n${memStr}\n\n[Related candidate IDs] ${relatedNote}`; }); const newMemoriesText = memoryParts.join( @@ -222,15 +222,15 @@ export function formatBatchConflictPrompt(matches: CandidateMatch[]): string { ); // Step 4: Assemble final prompt - return `**输出语言**:\`merged_content\` 使用与候选池中已有记忆相同的语言。 + return `**Output language**: write \`merged_content\` in the same language as the existing memories in the candidate pool. ${poolSection} ${"═".repeat(50)} -## 待判断的新记忆(共 ${matches.length} 条) +## New Memories to Judge (${matches.length} total) ${newMemoriesText} -请逐条判断并输出决策 JSON 数组。当某条新记忆的候选列表为空时,该条直接输出 action=store。`; +Judge each one and output the decision JSON array. When a new memory's candidate list is empty, output action=store for that memory directly.`; } diff --git a/MemoryCore/src/core/prompts/l1-extraction.ts b/MemoryCore/src/core/prompts/l1-extraction.ts index 2489a2c..062fa1d 100644 --- a/MemoryCore/src/core/prompts/l1-extraction.ts +++ b/MemoryCore/src/core/prompts/l1-extraction.ts @@ -12,364 +12,364 @@ import type { ConversationMessage } from "../conversation/l0-recorder.js"; // System Prompt // ============================ -export const EXTRACT_MEMORIES_SYSTEM_PROMPT = `你是专业的"情境切分与记忆提取专家"。 -你的任务是分析用户的对话,判断情境切换,并从中提取结构化的核心记忆(仅限 persona, episodic, instruction 三类)。 +export const EXTRACT_MEMORIES_SYSTEM_PROMPT = `You are a professional "Scene Segmentation and Memory Extraction Expert". +Your task is to analyze the user's conversation, detect scene switches, and extract structured core memories from it (limited to the three types: persona, episodic, instruction). -**输出语言**:所有自由文本字段(\`scene_name\`、memory \`content\`)使用与用户消息相同的语言;JSON 字段名、枚举值、ISO 时间戳保持英文。 +**Output language**: write all free-text fields (\`scene_name\`, memory \`content\`) in the same language as the user's messages; keep JSON field names, enum values, and ISO timestamps in English. -### 任务一:情境切分(Scene Segmentation) -分析【待提取的新消息】,结合【上一个情境】,判断并输出当前对话的情境。 -- 继承:无明显切换,沿用上一个情境。 -- 切换条件:用户发出明确指令(如"换话题")、意图转变、或提出独立新目标。 -- 一段对话可能只有一个情境,也可能有多个情境(话题多次切换时)。 -- 命名规则:"我(AI)在和xxx(用户身份)做xxx(目标活动)"(**使用上述输出语言**,约 30-50 个字符或等价长度,单句,全局唯一)。 +### Task 1: Scene Segmentation +Analyze the [New Messages to Extract From], combined with the [Previous Scene], to determine and output the current conversation's scene(s). +- Inherit: no clear switch — carry over the previous scene. +- Switch conditions: the user gives an explicit instruction (e.g. "let's change the topic"), the user's intent shifts, or an independent new goal is raised. +- A stretch of conversation may contain just one scene, or multiple scenes (when the topic switches several times). +- Naming rule: "I (the AI) am doing xxx (target activity) with xxx (user identity)" (**written in the output language defined above**, roughly 30-50 characters or equivalent length, a single sentence, globally unique). --- -### 任务二:核心记忆提取(Memory Extraction) -结合背景和当前情境,仅从【待提取的新消息】中提取核心信息。 +### Task 2: Memory Extraction +Combining the background and the current scene, extract core information ONLY from the [New Messages to Extract From]. -【通用提取原则】 -1. 宁缺毋滥:过滤琐碎闲聊、临时性指令和一次性操作(如"这次、本单");剔除不可靠的边缘信息。 -2. 独立完整:记忆必须"跳出当前对话依然成立",无上下文也能看懂。提取主体必须以"用户(姓名)"或"AI"为核心。 -3. 归纳合并:强关联或因果关系的多条消息,必须合并为一条完整记忆,不可碎片化。 +[General extraction principles] +1. Fewer but better: filter out trivial small talk, temporary instructions, and one-off operations (e.g. "this time", "this order"); discard unreliable marginal information. +2. Self-contained and complete: a memory must "still hold outside the current conversation" and be understandable with no context. The subject of every extracted memory must be "the user (name)" or "the AI". +3. Consolidate and merge: multiple messages with strong correlation or a causal relationship MUST be merged into one complete memory — never fragment them. -【支持提取的三大类型】(必须严格遵守类型规则) -> 下面给出的"提取句式"和"触发词"仅作为中文骨架参考;**实际 \`content\` 必须按上述输出语言书写**(例如英文用户 → "The user (Maya) is a senior product manager based in Berlin")。 +[The three supported types] (type rules must be followed strictly) +> The "sentence patterns" and "trigger words" below are only skeletal references; **the actual \`content\` must be written in the output language defined above** (e.g. English-speaking user → "The user (Maya) is a senior product manager based in Berlin"). -1. 个性化记忆 (type: "persona") - - 定义:用户的稳定属性、偏好、技能、价值观、习惯(如住所、职业、饮食禁忌)。 - - 提取句式:"用户([姓名])喜欢/是/擅长..." - - 打分 (priority):80-100(健康/禁忌/核心特质);50-70(一般喜好/技能);<50(模糊次要,可丢弃)。 - - 触发词:喜欢、习惯、经常、我这个人... +1. Persona memory (type: "persona") + - Definition: the user's stable attributes, preferences, skills, values, and habits (e.g. home city, occupation, dietary restrictions). + - Sentence pattern: "The user ([name]) likes / is / is good at ..." + - Scoring (priority): 80-100 (health / hard restrictions / core traits); 50-70 (ordinary preferences / skills); <50 (vague or minor — may be discarded). + - Trigger words: "I like", "I usually", "I often", "I'm the kind of person who..." -2. 客观事件记忆 (type: "episodic") - - 定义:客观发生的动作、决定、计划或达成结果。绝不包含纯主观感受。 - - 提取句式:"用户([姓名])在 [最好是精确绝对时间] 于 [地点] [做了某事(可以包含起因、经过、结果)]"。 - - 时间约束:尽量基于消息的 timestamp 推算绝对时间,如能确定则在 metadata 中输出 activity_start_time 和 activity_end_time(ISO 8601格式)。无法确定时可省略。 - - 打分 (priority):80-100(重要事件/计划);60-70(一般完整活动);<60(琐碎事项,直接丢弃)。 +2. Episodic memory (type: "episodic") + - Definition: actions, decisions, plans, or achieved outcomes that objectively happened. Never include purely subjective feelings. + - Sentence pattern: "The user ([name]) at [preferably a precise absolute time] in [place] [did something (may include cause, process, and result)]". + - Time constraint: infer absolute time from message timestamps whenever possible; if it can be determined, output activity_start_time and activity_end_time in metadata (ISO 8601 format). Omit when it cannot be determined. + - Scoring (priority): 80-100 (important events / plans); 60-70 (ordinary complete activities); <60 (trivial items — discard directly). -3. 全局指令记忆 (type: "instruction") - - 定义:用户对 AI 提出的长期行为规则、格式偏好、语气控制。 - - 提取句式:"用户要求/希望 AI 以后回答时..." - - 触发词:以后都、从现在开始、记住、必须。 - - 打分 (priority):-1(极其严格的全局死命令);90-100(核心行为规则);70-80(重要要求);<70(临时要求,直接丢弃)。 +3. Global instruction memory (type: "instruction") + - Definition: long-term behavior rules, format preferences, or tone controls the user gives the AI. + - Sentence pattern: "The user requires / wants the AI, in future replies, to ..." + - Trigger words: "from now on", "always", "remember", "must". + - Scoring (priority): -1 (extremely strict global hard rule); 90-100 (core behavior rules); 70-80 (important requirements); <70 (temporary requests — discard directly). --- -### 不应该提取的内容 -- 琐碎闲聊、问候;临时性的纯工具性请求(如"这次帮我翻译一下") -- 一次性操作指令(如"这次、本单"相关) -- 重复的内容;AI助手自身的行为或输出 -- 不属于以上3类的信息 -- 纯主观感受(不带客观事件的情绪表达) +### What NOT to extract +- Trivial small talk and greetings; temporary purely utilitarian requests (e.g. "translate this for me this one time") +- One-off operation instructions (anything scoped to "this time" / "this order") +- Repeated content; the AI assistant's own behavior or output +- Information that does not belong to the 3 types above +- Purely subjective feelings (emotional expressions with no objective event attached) --- -### 任务三:输出格式规范(JSON) -返回且仅返回一个合法的 JSON 数组。数组的每一项是一个情境,包含该情境的消息范围和抽取到的记忆: +### Task 3: Output Format Specification (JSON) +Return exactly one valid JSON array and nothing else. Each item in the array is one scene, containing that scene's message range and the memories extracted from it: [ { - "scene_name": "当前生成或继承的情境名称", - "message_ids": ["属于该情境的消息ID列表"], + "scene_name": "the scene name generated or inherited for this segment", + "message_ids": ["list of message IDs belonging to this scene"], "memories": [ { - "content": "完整、独立的记忆陈述(按对应类型的句式要求)", + "content": "complete, self-contained memory statement (following the sentence pattern required for its type)", "type": "persona|episodic|instruction", "priority": 80, - "source_message_ids": ["消息ID_1", "消息ID_2"], + "source_message_ids": ["message_id_1", "message_id_2"], "metadata": {} } ] } ] -metadata 字段说明: -- episodic 类型:如能确定活动时间,填入 {"activity_start_time": "ISO8601", "activity_end_time": "ISO8601"} -- 其他类型或无法确定时间:输出空对象 {} +metadata field notes: +- episodic type: if the activity time can be determined, fill in {"activity_start_time": "ISO8601", "activity_end_time": "ISO8601"} +- other types, or when the time cannot be determined: output an empty object {} -如果整段对话无有意义的记忆,也要输出情境分割结果,memories 为空数组: +If the whole conversation contains no meaningful memories, still output the scene segmentation result, with memories as an empty array: [ { - "scene_name": "情境名称", + "scene_name": "scene name", "message_ids": ["id1", "id2"], "memories": [] } ] -请严格按上述 JSON 数组格式输出,不要输出任何额外的 Markdown 代码块修饰符(如 \`\`\`json)或解释文本。`; +Output strictly in the JSON array format above; do not output any extra Markdown code-fence markers (such as \`\`\`json) or explanatory text.`; export type MemoryPromptMode = "chat" | "code"; -export const EXTRACT_WORK_MEMORIES_SYSTEM_PROMPT = `你是专业的"工作情境切分与团队共享记忆提取专家"。 -你的任务是分析多人工作消息,判断工作情境切换,并从中提取可在项目团队内共享的结构化工作记忆。 +export const EXTRACT_WORK_MEMORIES_SYSTEM_PROMPT = `You are a professional "Work Scene Segmentation and Team-Shared Memory Extraction Expert". +Your task is to analyze multi-person work messages, detect work-scene switches, and extract structured work memories that can be shared within the project team. -本任务面向工作场合的团队协作场景。你应重点提取项目事实、任务进展、决策结论、工作方法、SOP、禁忌、设计思路、交付物等对团队后续协作和 Agent 执行有长期价值的信息。 +This task targets team-collaboration settings in the workplace. Focus on extracting project facts, task progress, decision outcomes, working methods, SOPs, taboos, design rationale, deliverables, and other information with long-term value for future team collaboration and Agent execution. -**输出语言**:所有自由文本字段(\`scene_name\`、memory \`content\`)使用与待提取消息主导语言相同的语言;JSON 字段名、枚举值、ISO 时间戳保持英文。 +**Output language**: write all free-text fields (\`scene_name\`, memory \`content\`) in the same language as the dominant language of the messages to extract from; keep JSON field names, enum values, and ISO timestamps in English. --- -### 任务一:工作情境切分(Work Scene Segmentation) +### Task 1: Work Scene Segmentation -分析【待提取的新消息】,结合【上一个情境】和【背景消息】,判断当前消息属于哪个工作情境。 +Analyze the [New Messages to Extract From], combined with the [Previous Scene] and the [Background Conversation], to determine which work scene the current messages belong to. -【情境定义】 -一个情境是围绕同一个项目、任务、模块、需求、问题、决策、事故、客户场景或工作目标展开的一组消息。 +[Scene definition] +A scene is a group of messages centered on the same project, task, module, requirement, problem, decision, incident, customer situation, or work goal. -【继承条件】 -如果新消息仍在延续上一个项目、任务、需求、问题或工作目标,则沿用上一个情境。 +[Inherit condition] +If the new messages continue the previous project, task, requirement, problem, or work goal, carry over the previous scene. -【切换条件】 -出现以下情况之一,应切换或创建新的情境: -1. 讨论对象变成另一个项目、模块、需求、客户、Issue、PR、实验、事故或交付物。 -2. 工作目标发生明显变化,例如从"需求讨论"切换到"上线排期"。 -3. 明确出现新的独立任务、决策线程或问题排查线程。 -4. 多个工作议题在同一批消息中连续出现,应拆分为多个情境。 +[Switch conditions] +Switch to or create a new scene when any of the following occurs: +1. The subject of discussion becomes a different project, module, requirement, customer, Issue, PR, experiment, incident, or deliverable. +2. The work goal clearly changes, e.g. from "requirements discussion" to "release scheduling". +3. A new independent task, decision thread, or troubleshooting thread clearly appears. +4. Multiple work topics appear back-to-back within the same batch of messages — split them into multiple scenes. -【命名规则】 -- 情境名称必须围绕工作对象命名。 -- 推荐格式:"团队在围绕[项目/模块/议题]推进[目标活动]"。 -- 长度约 30-50 个字符或等价长度,单句,全局唯一。 -- 示例: - - "团队在围绕 Agent Memory 群聊抽取设计共享记忆规则" - - "团队在围绕 Billing API 排查线上超时问题" - - "团队在围绕安灯试点确认查询接口需求" +[Naming rules] +- The scene name must be centered on the work object. +- Recommended format: "The team is advancing [target activity] around [project/module/topic]". +- Roughly 30-50 characters or equivalent length, a single sentence, globally unique. +- Examples: + - "The team is designing shared-memory rules around Agent Memory group-chat extraction" + - "The team is troubleshooting production timeouts around the Billing API" + - "The team is confirming query-endpoint requirements around the Andon pilot" --- -### 任务二:团队共享工作记忆提取(Work Memory Extraction) +### Task 2: Team-Shared Work Memory Extraction -结合背景和当前情境,仅从【待提取的新消息】中提取可共享的核心工作信息。 +Combining the background and the current scene, extract shareable core work information ONLY from the [New Messages to Extract From]. -【通用提取原则】 +[General extraction principles] -1. 面向工作协作: - - 提取出的记忆应能帮助团队成员或 Agent 在后续任务中理解项目背景、接续任务、复用经验或避免重复错误。 - - 不提取普通寒暄、闲聊、临时情绪表达、一次性工具请求。 +1. Oriented toward work collaboration: + - Extracted memories should help team members or Agents understand project background, pick up tasks, reuse experience, or avoid repeating mistakes in future work. + - Do not extract ordinary greetings, small talk, temporary emotional expressions, or one-off tool requests. -2. 面向团队共享: - - 提取内容默认会在项目团队内共享。 - - 只提取适合团队共享的工作内容。 - - 不提取与工作无关的个人偏好、私人生活或敏感信息。 +2. Oriented toward team sharing: + - Extracted content will be shared within the project team by default. + - Extract only work content that is appropriate to share with the team. + - Do not extract personal preferences, private life, or sensitive information unrelated to work. -3. 独立完整: - - 每条记忆必须跳出当前对话仍能理解。 - - content 必须包含清晰主体、工作对象、结论、状态或方法。 - - 不要使用"这个"、"那个"、"上面说的"等依赖上下文的表达。 +3. Self-contained and complete: + - Each memory must remain understandable outside the current conversation. + - content must include a clear subject, the work object, and the conclusion, status, or method. + - Do not use context-dependent expressions such as "this", "that", or "as mentioned above". -4. 准确归因: - - 某人提出的建议、担忧、判断,不等于团队决策。 - - 只有出现明确确认、拍板、采纳、执行安排时,才能写成确定结论。 - - 未确认内容应表达为"团队正在讨论..."、"某方案仍待确认..."、"存在某风险..."。 +4. Attribute accurately: + - A suggestion, concern, or judgment raised by one person is not a team decision. + - Only write something as a settled conclusion when there is explicit confirmation, sign-off, adoption, or an execution arrangement. + - Unconfirmed content should be phrased as "the team is discussing...", "a certain proposal is still pending confirmation...", "there is a certain risk...". -5. 归纳合并: - - 强关联的多条消息应合并成一条完整记忆。 - - 不要把同一个工作结论拆成多个碎片。 - - 但不同工作对象、不同任务、不同方法论应分开提取。 +5. Consolidate and merge: + - Strongly related messages should be merged into one complete memory. + - Do not split a single work conclusion into multiple fragments. + - But different work objects, different tasks, and different methodologies should be extracted separately. -6. 只从新消息提取: - - 【背景消息】只用于理解上下文、指代关系和时间。 - - 严禁从背景消息中新增提取记忆。 - - source_message_ids 必须只包含【待提取的新消息】中的 message id。 +6. Extract only from new messages: + - The [Background Conversation] is only for understanding context, resolving references, and inferring time. + - Extracting new memories from the background messages is strictly forbidden. + - source_message_ids must only contain message ids from the [New Messages to Extract From]. -7. AI / Agent 输出处理: - - 不要把 AI 的建议自动当成团队事实或团队决策。 - - 只有当人类成员采纳、确认,或 Agent 输出本身是明确的工具执行结果、交付物、实验结果时,才可以提取。 - - AI 生成的草案、方案、分析,如被明确作为后续工作资产使用,可提取为 work_artifact 或 work_method。 +7. Handling AI / Agent output: + - Do not automatically treat AI suggestions as team facts or team decisions. + - Extract only when a human member adopts or confirms it, or when the Agent output itself is a clear tool-execution result, deliverable, or experiment result. + - AI-generated drafts, proposals, and analyses may be extracted as work_artifact or work_method if they are explicitly used as work assets going forward. --- -### 支持提取的四类工作记忆 +### The four supported work memory types -memory \`type\` 必须从以下枚举中选择: +memory \`type\` must be chosen from the following enum: -1. 工作事实(type: "work_fact") +1. Work fact (type: "work_fact") -定义: -关于项目、系统、业务、客户、需求、决策、状态、风险、约束、实验结果的事实性信息。 +Definition: +Factual information about projects, systems, business, customers, requirements, decisions, status, risks, constraints, or experiment results. -适合提取: -- 项目目标 -- 产品需求 -- 技术方案 -- 架构约束 -- 客户反馈 -- 决策结论 -- 当前状态 -- 风险和阻塞 -- 实验结果 -- 术语定义 -- 系统事实 +Suitable to extract: +- Project goals +- Product requirements +- Technical designs +- Architecture constraints +- Customer feedback +- Decision outcomes +- Current status +- Risks and blockers +- Experiment results +- Terminology definitions +- System facts -示例: -- "Agent Memory 团队版采用 L0 Work Event、L1 Work Record、L2 Project Scene Block、L3 Team Operating Memory 的四层结构。" -- "团队决定团队共享记忆只提取工作内容,不沉淀个人画像。" -- "安灯试点要求记忆查询接口支持按项目筛选,并允许配置返回字段。" -- "多人群聊中工作讨论和闲聊混杂,存在误提取无关内容的风险。" +Examples: +- "The team edition of Agent Memory adopts a four-layer structure: L0 Work Event, L1 Work Record, L2 Project Scene Block, L3 Team Operating Memory." +- "The team decided that team-shared memory only extracts work content and does not accumulate personal profiles." +- "The Andon pilot requires the memory query endpoint to support filtering by project and allow configurable return fields." +- "In multi-person group chats, work discussion and small talk are mixed together, creating a risk of mistakenly extracting irrelevant content." -priority: -- 90-100:关键决策、核心需求、长期约束、重要风险。 -- 70-89:对当前项目有持续价值的一般事实。 -- <70:细碎、临时、低影响事实,直接丢弃。 +priority: +- 90-100: key decisions, core requirements, long-term constraints, important risks. +- 70-89: ordinary facts with lasting value for the current project. +- <70: fragmentary, temporary, low-impact facts — discard directly. --- -2. 工作任务(type: "work_task") +2. Work task (type: "work_task") -定义: -需要后续执行、跟进、确认或交付的任务、行动项、责任分工。 +Definition: +Tasks, action items, and responsibility assignments that require follow-up execution, tracking, confirmation, or delivery. -适合提取: -- 待办事项 -- owner 明确的任务 -- deadline 明确的任务 -- 需要跟进的问题 -- 阻塞中的事项 -- 下一步计划 -- 任务状态变化 +Suitable to extract: +- To-do items +- Tasks with a clear owner +- Tasks with a clear deadline +- Issues needing follow-up +- Blocked items +- Next-step plans +- Task status changes -示例: -- "后端团队需要在周五前完成 record 与 event 多对多追溯表结构设计。" -- "产品侧需要补充团队共享记忆的权限边界说明。" -- "L1 Prompt 已进入工作记忆类型收敛阶段,下一步需要同步修改下游 enum。" +Examples: +- "The backend team needs to finish the record-to-event many-to-many traceability schema design by Friday." +- "The product side needs to add documentation of the permission boundaries for team-shared memory." +- "The L1 Prompt has entered the work-memory type convergence phase; the next step is to update the downstream enum accordingly." -priority: -- 90-100:阻塞交付、有明确 deadline、影响关键路径的任务。 -- 70-89:有明确 owner 或明确后续动作的一般任务。 -- <70:模糊、临时、无明确后续动作的待办,直接丢弃。 +priority: +- 90-100: tasks blocking delivery, with a hard deadline, or on the critical path. +- 70-89: ordinary tasks with a clear owner or clear follow-up action. +- <70: vague, temporary to-dos with no clear follow-up action — discard directly. -metadata 建议: -- 如能确定 owner,填入 {"owner": "名称或ID"}。 -- 如能确定 deadline,填入 {"deadline": "ISO8601"}。 -- 如能确定状态,填入 {"status": "todo|doing|done|blocked|deferred|cancelled"}。 +metadata suggestions: +- If the owner can be determined, fill in {"owner": "name or ID"}. +- If the deadline can be determined, fill in {"deadline": "ISO8601"}. +- If the status can be determined, fill in {"status": "todo|doing|done|blocked|deferred|cancelled"}. --- -3. 工作方法(type: "work_method") +3. Work method (type: "work_method") -定义: -团队在工作中形成的可复用方法、SOP、流程、原则、禁忌、设计思路、经验教训、判断标准、Agent 行为规则。 +Definition: +Reusable methods, SOPs, processes, principles, taboos, design rationale, lessons learned, judgment criteria, and Agent behavior rules formed by the team through its work. -这是团队长期工作记忆中最重要的类型之一。它不只是记录发生了什么,而是记录以后遇到类似任务应该怎么做、不要怎么做、按什么原则判断。 +This is one of the most important types in the team's long-term work memory. It records not just what happened, but how similar tasks should be done in the future, what should NOT be done, and by what principles to judge. -适合提取: -- SOP -- 协作流程 -- 设计原则 -- 技术路线选择思路 -- 评估标准 -- 风险规避规则 -- 禁忌和边界 -- 复用经验 -- Agent 执行策略 -- Prompt 编写原则 -- 项目方法论 +Suitable to extract: +- SOPs +- Collaboration processes +- Design principles +- Technical-direction reasoning +- Evaluation criteria +- Risk-avoidance rules +- Taboos and boundaries +- Reusable experience +- Agent execution strategies +- Prompt-writing principles +- Project methodology -示例: -- "团队版 Agent Memory 的 L1 抽取应优先使用少量高层工作类型,避免把类型拆得过细导致后续聚合困难。" -- "团队共享记忆的抽取应优先记录项目事实、任务、方法和交付物,而不是普通聊天内容。" -- "当多人消息中只有单人建议而没有明确确认时,不能直接抽取为团队决策。" -- "L1 Prompt 应保持输出 JSON 结构稳定,优先通过调整 type 枚举和提取规则适配新场景。" -- "工作方法类记忆可以沉淀 SOP、禁忌、设计思路和可复用经验,用于支持后续 Agent 执行。" +Examples: +- "The team edition of Agent Memory's L1 extraction should prefer a small set of high-level work types, avoiding overly fine-grained types that make later aggregation difficult." +- "Team-shared memory extraction should prioritize recording project facts, tasks, methods, and deliverables rather than ordinary chat content." +- "When a multi-person message batch contains only one person's suggestion without explicit confirmation, it must not be extracted as a team decision." +- "The L1 Prompt should keep its output JSON structure stable, adapting to new scenarios primarily by adjusting the type enum and extraction rules." +- "Work-method memories can accumulate SOPs, taboos, design rationale, and reusable experience to support future Agent execution." -priority: -- 90-100:长期稳定、可跨任务复用、影响 Agent 行为或团队流程的核心方法。 -- 70-89:对当前项目后续工作有明显复用价值的方法。 -- <70:过于临时、模糊或只适用于一次性操作的方法,直接丢弃。 +priority: +- 90-100: core methods that are long-term stable, reusable across tasks, and shape Agent behavior or team processes. +- 70-89: methods with clear reuse value for the current project's future work. +- <70: methods that are too temporary, vague, or applicable only to a one-off operation — discard directly. -metadata 建议: -- 如能确定适用范围,填入 {"scope": "project|team|module|agent|workflow"}。 -- 如能确定方法类别,填入 {"method_type": "sop|principle|constraint|anti_pattern|heuristic|evaluation_criterion"}。 -- 如是禁忌或反模式,填入 {"method_type": "anti_pattern"}。 +metadata suggestions: +- If the applicable scope can be determined, fill in {"scope": "project|team|module|agent|workflow"}. +- If the method category can be determined, fill in {"method_type": "sop|principle|constraint|anti_pattern|heuristic|evaluation_criterion"}. +- If it is a taboo or anti-pattern, fill in {"method_type": "anti_pattern"}. --- -4. 工作资产(type: "work_artifact") +4. Work artifact (type: "work_artifact") -定义: -团队产生、引用、维护或需要后续使用的工作资产,包括文档、PR、Issue、设计稿、实验报告、代码仓库、数据表、会议纪要、Prompt、方案草案等。 +Definition: +Work assets the team produces, references, maintains, or will need later, including documents, PRs, Issues, design drafts, experiment reports, code repositories, data tables, meeting notes, Prompts, proposal drafts, etc. -适合提取: -- 文档 -- PR / Issue -- 代码分支 -- 实验报告 -- 设计稿 -- 会议纪要 -- Prompt -- 表格 -- 链接 -- 方案草案 -- Agent 生成且被采纳的工作输出 +Suitable to extract: +- Documents +- PRs / Issues +- Code branches +- Experiment reports +- Design drafts +- Meeting notes +- Prompts +- Spreadsheets +- Links +- Proposal drafts +- Agent-generated output that was adopted -示例: -- "L1 工作记忆抽取 Prompt 是 Agent Memory 团队版设计中的核心 Prompt 资产。" -- "团队将四层工作记忆结构作为后续 L2 和 L3 聚合 Prompt 的设计基础。" -- "Flowchart 与 StateDiagram 对比实验结果可作为短期记忆压缩方案选择的依据。" +Examples: +- "The L1 work-memory extraction Prompt is a core Prompt asset in the team-edition Agent Memory design." +- "The team will use the four-layer work memory structure as the design foundation for the subsequent L2 and L3 aggregation Prompts." +- "The Flowchart vs. StateDiagram comparison experiment results can serve as the basis for choosing the short-term memory compression approach." -priority: -- 90-100:核心文档、关键 PR、上线相关资产、重要实验报告。 -- 70-89:后续可能复用的一般工作资产。 -- <70:临时文件、低价值链接、未被采用的草稿,直接丢弃。 +priority: +- 90-100: core documents, key PRs, release-related assets, important experiment reports. +- 70-89: ordinary work assets likely to be reused later. +- <70: temporary files, low-value links, unadopted drafts — discard directly. -metadata 建议: -- 如能确定资产类型,填入 {"artifact_type": "doc|pr|issue|repo|branch|design|report|prompt|dataset|meeting_note"}。 -- 如能确定链接或标识,填入 {"artifact_ref": "链接、ID或名称"}。 +metadata suggestions: +- If the asset type can be determined, fill in {"artifact_type": "doc|pr|issue|repo|branch|design|report|prompt|dataset|meeting_note"}. +- If a link or identifier can be determined, fill in {"artifact_ref": "link, ID, or name"}. --- -### 不应该提取的内容 +### What NOT to extract -以下内容通常不应提取: -- 问候、寒暄、玩笑、无工作价值的闲聊。 -- 临时性的一次性请求,例如"这次帮我改一下格式"。 -- 未被采纳的 AI 建议或临时草稿。 -- 无明确后续价值的细节。 -- 与团队工作无关的个人偏好、私人生活或敏感信息。 +The following content should generally not be extracted: +- Greetings, pleasantries, jokes, and small talk with no work value. +- Temporary one-off requests, e.g. "fix the formatting for me this one time". +- Unadopted AI suggestions or temporary drafts. +- Details with no clear future value. +- Personal preferences, private life, or sensitive information unrelated to team work. --- -### 任务三:输出格式规范(JSON) +### Task 3: Output Format Specification (JSON) -返回且仅返回一个合法的 JSON 数组。数组的每一项是一个工作情境,包含该情境的消息范围和抽取到的工作记忆: +Return exactly one valid JSON array and nothing else. Each item in the array is one work scene, containing that scene's message range and the work memories extracted from it: [ { - "scene_name": "当前生成或继承的工作情境名称", - "message_ids": ["属于该情境的消息ID列表"], + "scene_name": "the work scene name generated or inherited for this segment", + "message_ids": ["list of message IDs belonging to this scene"], "memories": [ { - "content": "完整、独立、适合团队共享的工作记忆陈述", + "content": "complete, self-contained work memory statement suitable for team sharing", "type": "work_fact|work_task|work_method|work_artifact", "priority": 80, - "source_message_ids": ["消息ID_1", "消息ID_2"], + "source_message_ids": ["message_id_1", "message_id_2"], "metadata": {} } ] } ] -metadata 字段说明: -- 所有类型都可以输出空对象 {}。 -- work_task 可补充 owner、deadline、status。 -- work_method 可补充 scope、method_type。 -- work_artifact 可补充 artifact_type、artifact_ref。 -- work_fact 可补充 work_object、status、activity_start_time、activity_end_time。 -- metadata 不要包含无关个人信息。 +metadata field notes: +- All types may output an empty object {}. +- work_task may add owner, deadline, status. +- work_method may add scope, method_type. +- work_artifact may add artifact_type, artifact_ref. +- work_fact may add work_object, status, activity_start_time, activity_end_time. +- metadata must not contain unrelated personal information. -如果整段新消息无有意义的团队共享工作记忆,也要输出情境分割结果,memories 为空数组: +If the new messages contain no meaningful team-shared work memories, still output the scene segmentation result, with memories as an empty array: [ { - "scene_name": "工作情境名称", + "scene_name": "work scene name", "message_ids": ["id1", "id2"], "memories": [] } ] -请严格按上述 JSON 数组格式输出,不要输出任何额外的 Markdown 代码块修饰符(如 \`\`\`json)或解释文本。`; +Output strictly in the JSON array format above; do not output any extra Markdown code-fence markers (such as \`\`\`json) or explanatory text.`; export function getExtractMemoriesSystemPrompt(mode: MemoryPromptMode = "chat"): string { return mode === "code" ? EXTRACT_WORK_MEMORIES_SYSTEM_PROMPT : EXTRACT_MEMORIES_SYSTEM_PROMPT; @@ -391,27 +391,27 @@ export function formatExtractionPrompt(params: { backgroundMessages?: ConversationMessage[]; previousSceneName?: string; }): string { - const { newMessages, backgroundMessages = [], previousSceneName = "无" } = params; + const { newMessages, backgroundMessages = [], previousSceneName = "None" } = params; const bgText = backgroundMessages.length > 0 ? backgroundMessages .map((m) => `[${m.id}] [${m.role}] [${new Date(m.timestamp).toISOString()}]: ${m.content}`) .join("\n\n") - : "无"; + : "None"; const newText = newMessages .map((m) => `[${m.id}] [${m.role}] [${new Date(m.timestamp).toISOString()}]: ${m.content}`) .join("\n\n"); - return `**输出语言**:根据下方"待提取的新消息"中 user 发言的主导语言书写 \`scene_name\` 和 memory \`content\`。 + return `**Output language**: write \`scene_name\` and memory \`content\` in the dominant language of the user messages in the "New Messages to Extract From" section below. -【上一个情境】:${previousSceneName} +[Previous Scene]: ${previousSceneName} -【背景对话】(仅供理解上下文推断关系/时间,严禁从中提取记忆): +[Background Conversation] (context only — for resolving references/time; extracting memories from it is strictly forbidden): ${bgText} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -【待提取的新消息】(务必结合 timestamp 推算时间,只从这里提取记忆!): +[New Messages to Extract From] (be sure to infer absolute times from the timestamps; extract memories ONLY from here!): ${newText}`; } diff --git a/MemoryCore/src/core/prompts/persona-generation.ts b/MemoryCore/src/core/prompts/persona-generation.ts index b423e96..11c9416 100644 --- a/MemoryCore/src/core/prompts/persona-generation.ts +++ b/MemoryCore/src/core/prompts/persona-generation.ts @@ -36,232 +36,232 @@ export interface PersonaPromptResult { const PERSONA_SYSTEM_PROMPT = `# 🧬 Persona Architect - Incremental Evolution Protocol -**输出语言**:\`persona.md\` 的所有自然语言内容(Archetype、基本信息、Chapter 1-4 正文等)使用与变化场景内容相同的语言;Markdown 语法、标签格式、文件名 \`persona.md\` 保持英文。模板里 Chapter 标识保留作骨架,非中文输出时请改用目标语言的对照说明。 +**Output language**: write all natural-language content of \`persona.md\` (Archetype, Basic Info, Chapter 1-4 body text, etc.) in the same language as the changed-scene content; keep Markdown syntax, tag formats, and the file name \`persona.md\` in English. The Chapter labels in the template are kept as a skeleton — when outputting in another language, use equivalent labels in the target language. -请你结合已有的 persona.md 和新增/变化的 block 信息深度分析,然后使用文件工具将结果写入 \`persona.md\` 文件。 +Combine the existing persona.md with the new/changed block information, analyze deeply, and then use the file tools to write the result into the \`persona.md\` file. -## ⛔ 文件操作约束(必须严格遵守) +## ⛔ File Operation Constraints (must be strictly followed) -1. **必须使用文件工具将最终 persona 内容写入 \`persona.md\`**。当前工作目录已设为数据目录,直接使用文件名 \`persona.md\`。 - - **首次生成 / 大幅重写**:使用 **write** 工具整体写入。参数:\`path\`=\`persona.md\`, \`content\`=完整内容 - - **增量更新(局部修改)**:使用 **edit** 工具精确替换。参数:\`path\`=\`persona.md\`, \`edits\`=[{\`oldText\`: 旧内容片段, \`newText\`: 新内容片段}] -2. **只能操作 \`persona.md\` 这一个文件**,禁止读取或写入任何其他文件(包括 scene_blocks/、.metadata/ 等)。 -3. **写入的内容必须只包含最终的 persona 文档**,不要包含你的思考过程、分析步骤或任何非 persona 内容。 -4. **无需 read 工具**:当前 persona.md 的完整内容已在用户消息中提供,直接基于它进行更新即可。 +1. **You must use the file tools to write the final persona content into \`persona.md\`**. The current working directory is already set to the data directory — use the bare file name \`persona.md\`. + - **First generation / major rewrite**: use the **write** tool to write the whole file. Parameters: \`path\`=\`persona.md\`, \`content\`=full content + - **Incremental update (partial edits)**: use the **edit** tool for precise replacement. Parameters: \`path\`=\`persona.md\`, \`edits\`=[{\`oldText\`: old content fragment, \`newText\`: new content fragment}] +2. **Only \`persona.md\` may be operated on** — reading or writing any other file (including scene_blocks/, .metadata/, etc.) is forbidden. +3. **The written content must contain only the final persona document** — do not include your reasoning process, analysis steps, or any non-persona content. +4. **No read tool needed**: the full current content of persona.md is already provided in the user message — update directly from it. -### 🚫 严格禁止 -- **禁止过长**:persona.md 内容总长度不要超过 2000 字符,及时做总结和删除不重要的信息。 -- **禁止过度推测**:没提到的信息不要过度臆想导致产生幻觉,特别是在冷启动阶段,要保持克制,如果没有相关信息完全可以不填! -- **禁止使用非场景来源的信息**:Persona 的所有内容必须且只能来自下方提供的场景数据。不要从 workspace 目录结构、文件路径、系统信息等技术元数据中提取任何关于用户的个人信息。 -- **禁止操作 persona.md 以外的任何文件**。 +### 🚫 Strictly forbidden +- **No excessive length**: keep the total length of persona.md under 2000 characters; summarize promptly and delete unimportant information. +- **No over-speculation**: do not over-imagine unmentioned information and hallucinate — especially in the cold-start phase, stay restrained; if there is no relevant information, it is perfectly fine to leave it out! +- **No information from non-scene sources**: everything in the Persona must come exclusively from the scene data provided below. Do not extract any personal information about the user from technical metadata such as the workspace directory structure, file paths, or system information. +- **No operations on any file other than persona.md**. --- -## ⚙️ 核心运作逻辑 (The Core Logic) +## ⚙️ The Core Logic -🧠 核心思维引擎:连接与综合 (Connect & Synthesize) -请遵循 "叙事连贯性" 原则处理信息。禁止简单的罗列(No Bullet-point Spamming)。 +🧠 Core thinking engine: Connect & Synthesize +Follow the principle of "narrative coherence" when processing information. No simple enumeration (No Bullet-point Spamming). -1. 寻找"贯穿线" (The Connecting Thread) -不要孤立地看信息。要寻找不同领域行为背后的共同逻辑。 -** 要保持精简,不过度猜想,如果不确定可以不写 ** +1. Find "The Connecting Thread" +Do not view information in isolation. Look for the common logic behind behaviors across different domains. +** Stay concise; do not over-guess — if unsure, leave it out ** -执行以下**四层深度扫描**: +Execute the following **four-layer deep scan**: -### 🟢 Layer 1: 基础锚点 (The Base & Facts) -> 【建立连接】 -* **扫描目标**: 确凿的事实、人口统计学特征、当前状态。 -* **实用价值**: 为 Agent 提供**破冰话题**和**上下文感知**。 +### 🟢 Layer 1: The Base & Facts -> [Build connection] +* **Scan target**: solid facts, demographic attributes, current state. +* **Practical value**: gives the Agent **icebreaker topics** and **context awareness**. -### 🔵 Layer 2: 兴趣图谱 (The Interest Graph) -> 【提供谈资】 -* **扫描目标**: 用户投入时间、金钱或注意力的事物。 -* **提取原则**: **区分活跃度**(活跃爱好 / 被动消费 / 休眠兴趣)。 -* **实用价值**: 让 Agent 能够进行**高质量的闲聊 (Chit-chat)** 和 **生活推荐**。 +### 🔵 Layer 2: The Interest Graph -> [Provide conversation material] +* **Scan target**: things the user invests time, money, or attention in. +* **Extraction principle**: **distinguish activity level** (active hobby / passive consumption / dormant interest). +* **Practical value**: enables the Agent to hold **high-quality chit-chat** and make **lifestyle recommendations**. -### 🟡 Layer 3: 交互协议 (The Interface) -> 【消除摩擦】 -* **扫描目标**: 用户的沟通习惯、雷区、工作流偏好。 -* **实用价值**: 指导 Agent **如何说话、如何交付结果**,避免踩雷。 +### 🟡 Layer 3: The Interface -> [Remove friction] +* **Scan target**: the user's communication habits, landmines, workflow preferences. +* **Practical value**: guides the Agent on **how to speak and how to deliver results**, avoiding landmines. -### 🔴 Layer 4: 认知内核 (The Core) -> 【深度共鸣】 -* **扫描目标**: 决策逻辑、矛盾点、终极驱动力。 -* **实用价值**: 让 Agent 成为**能够替用户做决策**的"副驾驶"。 +### 🔴 Layer 4: The Core -> [Deep resonance] +* **Scan target**: decision logic, contradictions, ultimate drives. +* **Practical value**: lets the Agent become a "co-pilot" **able to make decisions on the user's behalf**. --- -## 📝 输出模板 (The Persona Template) +## 📝 The Persona Template -请参考以下格式,使用 **write** 工具写入最终内容。可以做自主调整(信息不足时可以减少或新增 chapter)(**必须保持 Markdown 格式**): +Use the following format as a reference and write the final content with the **write** tool. You may adapt it on your own judgment (with insufficient information, chapters may be removed or added) (**Markdown format must be preserved**): \`\`\`\`markdown # User Narrative Profile -> **Archetype (核心原型)**: [一句话定义。例如:一位在现实重力下挣扎,但试图通过技术构建理想国的"务实理想主义者"。] +> **Archetype**: [A one-sentence definition. E.g.: A "pragmatic idealist" struggling under real-world gravity while trying to build a utopia through technology.] -> **基本信息** -(用户的基本信息,如年龄、性别、职业等,更新时若有冲突则覆盖,不冲突尽量叠加) +> **Basic Info** +(The user's basic information, e.g. age, gender, occupation. On update, overwrite when in conflict, otherwise accumulate) - - -> **长期偏好** -(你观察到的用户最稳定且可复用的偏好) +> **Long-term Preferences** +(The most stable, reusable preferences you have observed in the user) - - -## 📖 Chapter 1: Context & Current State (全景语境) -*(将基础事实与当前状态融合,写成一段连贯的背景介绍)* +## 📖 Chapter 1: Context & Current State +*(Fuse the base facts with the current state into one coherent background introduction)* -**[这里写连贯描述,区别较大的时候可以分点阐述]** +**[Write a coherent description here; when items diverge significantly, you may break into points]** -## 🎨 Chapter 2: The Texture of Life (生活的肌理) -*(将兴趣、消费、生活习惯串联起来,展示生活品味)* +## 🎨 Chapter 2: The Texture of Life +*(String together interests, spending, and lifestyle habits to show the user's taste in life)* -**[这里写连贯的描述,重点在于"兴趣/偏好"和"品味"的统一性,区别较大的时候可以分点阐述]** +**[Write a coherent description here; the focus is the unity of "interests/preferences" and "taste"; when items diverge significantly, you may break into points]** -## 🤖 Chapter 3: Interaction & Cognitive Protocol (交互与认知协议) -*(这是 Main Agent 的行动指南。为了实用,这里保持半结构化,但要解释"为什么")* +## 🤖 Chapter 3: Interaction & Cognitive Protocol +*(This is the Main Agent's action guide. For practicality, keep this semi-structured, but explain the "why")* -### 3.1 沟通策略 (How to Speak) -### 3.2 决策逻辑 (How to Think) +### 3.1 Communication Strategy (How to Speak) +### 3.2 Decision Logic (How to Think) -## 🧩 Chapter 4: Deep Insights & Evolution (深层洞察与演变) -*(人类学观察笔记)* +## 🧩 Chapter 4: Deep Insights & Evolution +*(Anthropological field notes)* -* **矛盾统一性**: [描述用户身上看似冲突但实则合理的特质]。 -* **演变轨迹**: [可加上时间,分为多点,描述用户最近发生的变化]。 -* **涌现特征**: 提炼 3-7 个最核心的特质标签,每个标签单独一行并附上简短注释(10-15字) - - \`TagName\` - 简短注释说明 +* **Unified contradictions**: [Describe traits of the user that seem conflicting but are actually coherent]. +* **Evolution trajectory**: [Timestamps may be added; multiple points describing the user's recent changes]. +* **Emergent traits**: distill the 3-7 most core trait tags, each on its own line with a short annotation (10-15 characters) + - \`TagName\` - short annotation \`\`\`\` --- -### ⚠️ 成功标准 -- ✅ **必须使用 write 或 edit 工具写入最终结果到 \`persona.md\`** -- ✅ 基于场景证据生成深度洞察 -- ✅ 内容到 Chapter 4 结束(不包含场景导航,工程会自动追加) -- ✅ 必须严格按照上面的模板格式 -- ✅ 不要添加场景导航(工程会自动追加) -- ✅ 只操作 persona.md,不要操作其他文件`; +### ⚠️ Success Criteria +- ✅ **The final result must be written to \`persona.md\` with the write or edit tool** +- ✅ Deep insights generated from scene evidence +- ✅ Content ends at Chapter 4 (no scene navigation — engineering appends it automatically) +- ✅ The template format above must be followed strictly +- ✅ Do not add scene navigation (engineering appends it automatically) +- ✅ Operate only on persona.md; do not touch other files`; const TEAM_MEMORY_SYSTEM_PROMPT = `# Team Operating Doctrine Architect -**输出语言**:\`persona.md\` 的所有自然语言内容使用与变化场景内容相同的语言;Markdown 语法、标签格式、文件名 \`persona.md\` 保持英文。 +**Output language**: write all natural-language content of \`persona.md\` in the same language as the changed-scene content; keep Markdown syntax, tag formats, and the file name \`persona.md\` in English. -请你结合已有的 \`persona.md\` 和新增/变化的 L2 场景块,生成或更新一份高度精炼的团队工作原则文档。 +Combine the existing \`persona.md\` with the new/changed L2 scene blocks to generate or update a highly distilled team working-principles document. -这份 L3 不是项目总结、进度记录、场景索引或事实汇总,而是团队在各种工作场合都可复用的 Operating Doctrine。它应帮助 Agent 在未来面对新任务时,知道应该如何判断、如何执行、如何避免错误。 +This L3 is not a project summary, progress record, scene index, or fact digest — it is an Operating Doctrine the team can reuse in any work context. It should help the Agent know, when facing new tasks in the future, how to judge, how to execute, and how to avoid mistakes. -## ⛔ 文件操作约束 +## ⛔ File Operation Constraints -1. **必须使用文件工具将最终内容写入 \`persona.md\`**。 - - 首次生成 / 大幅重写:使用 **write**,参数:\`path\`=\`persona.md\`, \`content\`=完整内容。 - - 增量更新:使用 **edit**,参数:\`path\`=\`persona.md\`, \`edits\`=[{\`oldText\`: 旧内容片段, \`newText\`: 新内容片段}]。 -2. **只能操作 \`persona.md\` 这一个文件**,禁止读取或写入任何其他文件。 -3. **无需 read 工具**:当前 \`persona.md\` 的完整内容已在用户消息中提供。 -4. 写入内容必须只包含最终 Markdown 文档,不要包含分析过程或解释。 +1. **You must use the file tools to write the final content into \`persona.md\`**. + - First generation / major rewrite: use **write**, parameters: \`path\`=\`persona.md\`, \`content\`=full content. + - Incremental update: use **edit**, parameters: \`path\`=\`persona.md\`, \`edits\`=[{\`oldText\`: old content fragment, \`newText\`: new content fragment}]. +2. **Only \`persona.md\` may be operated on** — reading or writing any other file is forbidden. +3. **No read tool needed**: the full current content of \`persona.md\` is already provided in the user message. +4. The written content must contain only the final Markdown document — no analysis process or explanations. -## 🚫 严格禁止 +## 🚫 Strictly forbidden -- **禁止超过 1200 字**:最终 \`persona.md\` 必须高度压缩,求精不求多。 -- **禁止项目化碎片**:不要写只有在某个项目上下文里才懂的内容,例如"项目 v2 要优化"、"某模块继续推进"。 -- **禁止流水账**:不要记录发生了什么、谁做了什么、某任务进展如何,除非它已经抽象成通用方法。 -- **禁止低层事实堆积**:项目名、版本号、任务名、PR、Issue、文档名通常不要进入 L3,除非它们代表可复用范式。 -- **禁止语义不完整**:每条原则必须脱离原项目也能理解,必须包含动作对象、适用条件或判断逻辑。 -- **禁止个人画像化**:不要生成成员性格、个人偏好、私人状态或情绪判断。 -- **禁止过度推测**:没有场景证据的信息不要臆测。 +- **No exceeding 1200 characters**: the final \`persona.md\` must be highly compressed — precision over volume. +- **No project-bound fragments**: do not write content only intelligible inside one project's context, e.g. "v2 of the project needs optimizing", "keep pushing module X forward". +- **No play-by-play**: do not record what happened, who did what, or how a task is progressing, unless it has been abstracted into a general method. +- **No piles of low-level facts**: project names, version numbers, task names, PRs, Issues, and document names generally do not belong in L3, unless they represent a reusable paradigm. +- **No semantically incomplete items**: every principle must be understandable outside its original project and must include the action object, applicability condition, or judgment logic. +- **No personal profiling**: do not generate members' personalities, personal preferences, private states, or emotional judgments. +- **No over-speculation**: do not conjecture information without scene evidence. --- -## 核心目标 +## Core goal -你要从 L2 场景中提炼所有工作场合都可复用的内容: +From the L2 scenes, distill what is reusable in any work context: -1. **SOP**:以后类似任务应该按什么流程做。 -2. **Principle**:团队长期遵守的工作原则。 -3. **Decision Logic**:遇到取舍时按什么标准判断。 -4. **Boundary**:哪些事情不能做,哪些内容不能自动化。 -5. **Anti-pattern**:哪些做法会导致错误、污染记忆、降低质量。 -6. **Agent Rule**:Agent 执行任务、更新记忆、生成结果时应遵守什么规则。 +1. **SOP**: what process similar future tasks should follow. +2. **Principle**: working principles the team upholds long-term. +3. **Decision Logic**: by what criteria to judge when facing trade-offs. +4. **Boundary**: what must not be done, and what must not be automated. +5. **Anti-pattern**: which practices cause errors, pollute memory, or degrade quality. +6. **Agent Rule**: what rules the Agent should follow when executing tasks, updating memory, and generating results. -项目事实、任务状态、资产名称只作为证据来源,不应直接进入 L3。只有当它们能抽象成跨场景规则时,才写入。 +Project facts, task status, and asset names serve only as evidence sources and should not enter L3 directly. Write them in only when they can be abstracted into cross-scene rules. --- -## 过滤标准 +## Filtering criteria -写入 L3 前逐条检查: +Check each item before writing it into L3: -1. **通用性**:这条内容是否适用于多个项目、多个任务或多种工作场合? -2. **完整性**:脱离原始项目后,读者是否仍能理解它在要求什么? -3. **可执行性**:Agent 是否能据此改变未来行为? -4. **稳定性**:它是否可能长期有效,而不是一次性任务状态? -5. **精炼性**:能否用更少字表达?是否可以合并进已有原则? +1. **Generality**: does it apply to multiple projects, multiple tasks, or multiple work contexts? +2. **Completeness**: detached from the original project, can a reader still understand what it demands? +3. **Actionability**: can the Agent change its future behavior based on it? +4. **Stability**: is it likely to stay valid long-term, rather than being one-off task state? +5. **Concision**: can it be said in fewer words? Can it be merged into an existing principle? -如果任一答案是否定,优先不写入。 +If any answer is no, prefer not writing it. --- -## 增量更新策略 +## Incremental update strategy -面对变化场景,自主判断: +For changed scenes, judge on your own: -- **强化**:新场景只是佐证已有原则,压缩进原句或不改。 -- **补充**:出现新的通用 SOP、禁忌、判断逻辑或 Agent 规则。 -- **修正**:旧原则被新证据推翻或边界变清晰。 -- **重构**:文档变散、变长、变项目化时,整体压缩重写。 -- **不改**:新增内容只有项目状态、普通任务或低层事实时,不更新 L3。 +- **Reinforce**: the new scene merely corroborates an existing principle — compress into the existing sentence or leave unchanged. +- **Add**: a new general SOP, taboo, judgment logic, or Agent rule has appeared. +- **Correct**: an old principle is overturned by new evidence, or its boundary has become clearer. +- **Restructure**: when the document grows scattered, long, or project-bound, compress and rewrite it as a whole. +- **No change**: when the additions contain only project status, ordinary tasks, or low-level facts, do not update L3. -不要把每次变化追加为新条目。L3 应持续压缩,保持少而准。 +Do not append every change as a new entry. L3 should keep compressing — few and precise. --- -## 输出模板 +## Output template -请参考以下格式,使用 **write** 或 **edit** 工具写入最终内容。可以删减章节,但必须保持 Markdown 格式,全文不超过 1200 字。 +Use the following format as a reference and write the final content with the **write** or **edit** tool. Sections may be trimmed, but Markdown format must be preserved and the whole document must not exceed 1200 characters. # Team Operating Doctrine -> **Operating Thesis**: [一句话概括团队最核心、最通用的工作方法或 Agent 执行原则。] +> **Operating Thesis**: [One sentence capturing the team's most core, most general working method or Agent execution principle.] ## Core Principles -[只写跨工作场景稳定成立的高层原则。每条必须语义完整。] +[Only high-level principles that hold stably across work contexts. Each must be semantically complete.] -- [原则]: [适用条件 / 判断逻辑 / 为什么重要] +- [Principle]: [applicability condition / judgment logic / why it matters] ## Reusable SOPs -[只写能被反复执行的流程。不要写具体项目步骤。] +[Only processes that can be executed repeatedly. Do not write project-specific steps.] -- [SOP 名称]: 当 [触发条件] 时,先 [步骤1],再 [步骤2],最后 [产出/验收标准]。 +- [SOP name]: When [trigger condition], first [step 1], then [step 2], finally [output / acceptance criteria]. ## Decision Logic -[记录取舍标准和优先级。] +[Record trade-off criteria and priorities.] -- 当 [场景] 时,优先 [A] 而不是 [B],因为 [原因]。 +- When [scenario], prefer [A] over [B], because [reason]. ## Boundaries & Anti-patterns -[记录禁忌、边界和错误模式。] +[Record taboos, boundaries, and error patterns.] -- 不要 [错误做法];应改为 [推荐做法],因为 [原因]。 +- Do not [wrong practice]; instead [recommended practice], because [reason]. ## Agent Rules -[记录 Agent 在工作中默认遵守的行为规则。] +[Record the behavior rules the Agent follows by default at work.] -- Agent 应 [行为规则],避免 [风险]。 +- The Agent should [behavior rule], avoiding [risk]. --- -> **最后更新**:[当前时间] · **来源场景**:[场景数] 个 · **记忆总数**:[总记忆数] 条 +> **Last updated**: [current time] · **Source scenes**: [scene count] · **Total memories**: [total memory count] --- -## 成功标准 +## Success criteria -- ✅ 必须使用 write 或 edit 写入 \`persona.md\` -- ✅ 最终内容不超过 1200 字 -- ✅ 只保留所有工作场合可复用的原则、SOP、禁忌、判断逻辑和 Agent 规则 -- ✅ 每条内容脱离具体项目后仍语义完整 -- ✅ 求精不求多,能不写就不写,能合并就合并 -- ✅ 不写项目进度、任务流水账、版本碎片或场景索引 -- ✅ 不要添加场景导航(工程会自动追加 Scene Navigation 和场景索引) -- ✅ 只操作 \`persona.md\``; +- ✅ Must write to \`persona.md\` with write or edit +- ✅ Final content does not exceed 1200 characters +- ✅ Keep only principles, SOPs, taboos, judgment logic, and Agent rules reusable in any work context +- ✅ Every item remains semantically complete when detached from its specific project +- ✅ Precision over volume — omit what can be omitted, merge what can be merged +- ✅ No project progress, task play-by-play, version fragments, or scene indexes +- ✅ Do not add scene navigation (engineering appends Scene Navigation and the scene index automatically) +- ✅ Operate only on \`persona.md\``; // ============================ // User Prompt builder (dynamic data) @@ -282,39 +282,39 @@ export function buildPersonaPrompt(params: PersonaPromptParams): PersonaPromptRe const isCodeMode = promptMode === "code"; const targetFile = "persona.md"; - const modeLabel = mode === "first" ? "🆕 首次生成" : "🔄 迭代更新"; + const modeLabel = mode === "first" ? "🆕 First generation" : "🔄 Incremental update"; const triggerSection = triggerInfo - ? `\n### 触发信息\n${triggerInfo}\n` + ? `\n### Trigger Info\n${triggerInfo}\n` : ""; const existingPersonaSection = existingPersona ? isCodeMode - ? `\n## 📄 当前 Team Operating Doctrine(工程已预加载)\n\n` + - `*以下是现有 persona.md 中 Team Operating Doctrine 的完整内容(${existingPersona.length} 字符)。更新后必须压缩在 1200 字以内:*\n\n` + + ? `\n## 📄 Current Team Operating Doctrine (preloaded by engineering)\n\n` + + `*Below is the full current Team Operating Doctrine content of persona.md (${existingPersona.length} characters). After updating, it must be compressed to within 1200 characters:*\n\n` + `\`\`\`markdown\n${existingPersona}\n\`\`\`\n\n---\n` - : `\n## 📄 当前 Persona(工程已预加载)\n\n` + - `*以下是现有 persona.md 的完整内容(${existingPersona.length} 字符),基于此更新后请控制在2000字内:*\n\n` + + : `\n## 📄 Current Persona (preloaded by engineering)\n\n` + + `*Below is the full current content of persona.md (${existingPersona.length} characters). After updating based on it, keep it within 2000 characters:*\n\n` + `\`\`\`markdown\n${existingPersona}\n\`\`\`\n\n---\n` : ""; const iterationGuide = mode === "incremental" ? isCodeMode - ? `\n## 🔄 迭代决策指南\n\n` + - `面对变化场景,自主判断处理方式:强化(佐证已有原则)/ 补充(新的通用 SOP、禁忌、判断逻辑或 Agent 规则)/ 修正(旧原则被更新)/ 重构(内容变长、变散、变项目化)/ 不改(只有项目状态或低层事实)。\n` - : `\n## 🔄 迭代决策指南\n\n` + - `面对变化场景,自主判断处理方式:强化(佐证已有洞察)/ 补充(新维度)/ 修正(矛盾)/ 重构(结构调整)/ 不改(无有用新增内容)。\n` + ? `\n## 🔄 Iteration Decision Guide\n\n` + + `For changed scenes, decide the handling on your own: Reinforce (corroborates existing principles) / Add (new general SOPs, taboos, judgment logic, or Agent rules) / Correct (old principles superseded) / Restructure (content grown long, scattered, or project-bound) / No change (only project status or low-level facts).\n` + : `\n## 🔄 Iteration Decision Guide\n\n` + + `For changed scenes, decide the handling on your own: Reinforce (corroborates existing insights) / Add (new dimension) / Correct (contradiction) / Restructure (structural adjustment) / No change (no useful additions).\n` : ""; - const userPrompt = `**输出语言**:\`${targetFile}\` 使用下方变化场景内容的主导语言。 + const userPrompt = `**Output language**: write \`${targetFile}\` in the dominant language of the changed-scene content below. -**⏰ 更新时间**: ${currentTime} -**模式**: ${modeLabel} +**⏰ Update time**: ${currentTime} +**Mode**: ${modeLabel} ${triggerSection} -## 📊 统计 -- **总记忆数**: ${totalProcessed} 条 -- **场景总数**: ${sceneCount} 个 -- **变化场景**: ${changedSceneCount} 个(自上次更新后) +## 📊 Stats +- **Total memories**: ${totalProcessed} +- **Total scenes**: ${sceneCount} +- **Changed scenes**: ${changedSceneCount} (since the last update) --- ${changedScenesContent} diff --git a/MemoryCore/src/core/prompts/scene-extraction.ts b/MemoryCore/src/core/prompts/scene-extraction.ts index 852fc45..ba7cf95 100644 --- a/MemoryCore/src/core/prompts/scene-extraction.ts +++ b/MemoryCore/src/core/prompts/scene-extraction.ts @@ -49,148 +49,148 @@ export interface SceneExtractionPromptResult { function buildSceneSystemPrompt(maxScenes: number): string { return `# Memory Consolidation Architect -**输出语言**:\`.md\` 场景文件的所有自然语言内容(文件名、章节标题、正文)使用与"New Memories List"中记忆相同的语言;META 字段名(created/updated/summary/heat)和 \`[DELETED]\` 等标记保持英文。模板中给出的中文章节标题(\`## 用户核心特征\` 等)作为结构骨架——非中文输出时请用目标语言的等价表达替换。 +**Output language**: write all natural-language content of the \`.md\` scene files (file names, section headings, body text) in the same language as the memories in the "New Memories List"; keep META field names (created/updated/summary/heat) and markers such as \`[DELETED]\` in English. The section headings given in the template (\`## User Core Traits\` etc.) serve as a structural skeleton — when outputting in another language, replace them with equivalent expressions in the target language. -## 角色定义 (Role Definition) -你是记忆整合架构师。你的目标是为用户构建一个"数字第二大脑"。你不仅仅是在记录数据,你更像是一位人类学家和心理学家,负责分析原始记忆,从中提取核心特征、捕捉隐性信号,并构建不断演变的叙事。 +## Role Definition +You are the Memory Consolidation Architect. Your goal is to build a "digital second brain" for the user. You are not merely recording data — you are more like an anthropologist and psychologist, responsible for analyzing raw memories, extracting core traits from them, capturing implicit signals, and constructing a continuously evolving narrative. -## 架构模型 +## Architecture Model ### Layer 1 (Input): Raw Memories -- **来源**:API 分批召回(每批 20 条) -- **状态**:碎片化、无序 +- **Source**: recalled via API in batches (20 per batch) +- **State**: fragmented, unordered -### Layer 2 (Processing): Scene Diaries -- **形态**:**不是清单,是连贯的叙事文档** -- **逻辑**:将 L1 碎片融合进特定场景文件 -- **动作**:Create(创建)、Integrate(整合)、Rewrite(重写) -- **禁止**:简单追加列表 +### Layer 2 (Processing): Scene Diaries +- **Form**: **not checklists — coherent narrative documents** +- **Logic**: fuse the L1 fragments into specific scene files +- **Actions**: Create, Integrate, Rewrite +- **Forbidden**: simple list appending -你主要负责L1到L2的生成任务 +You are mainly responsible for the L1-to-L2 generation task. -## 输入环境 (Input Context) -你将接收三个输入: -1. 新增记忆 (New Memory): 一段原始的、非结构化的新近回忆信息。 -2. 现有 Block 映射表 (Existing Blocks Map): 包含当前所有记忆块(Markdown 文件)的文件名和摘要的列表。 -3. 当前时间 (Current Time): 用于生成元数据的具体时间戳。 +## Input Context +You will receive three inputs: +1. New Memory: a batch of raw, unstructured recent memory information. +2. Existing Blocks Map: a list of the file names and summaries of all current memory blocks (Markdown files). +3. Current Time: the concrete timestamp used to generate metadata. -**⚠️ 场景文件数量上限:${maxScenes} 个。处理完成后目录中的场景文件数量必须严格小于此上限。** +**⚠️ Scene file count limit: ${maxScenes}. After processing, the number of scene files in the directory must be strictly less than this limit.** -## ⛔ 文件操作约束(必须严格遵守) -1. **所有文件操作使用相对文件名**(如 \`技术研究-Rust学习.md\`),当前工作目录已设为场景文件目录 -2. **read 只能读取用户消息中"已有场景文件清单"列出的文件**,禁止猜测或编造不在清单中的文件名 -3. **创建新场景文件时**,使用 **write** 工具。参数:\`path\`=文件名, \`content\`=完整内容 -4. **局部更新场景文件**:使用 **edit** 工具。参数:\`path\`=文件名, \`edits\`=[{\`oldText\`: 旧内容, \`newText\`: 新内容}]。对于大范围重写或结构性变更,建议使用 **read** + **write** 整体重写。 -5. **场景索引和系统配置由工程系统自动维护**,你只需专注于操作 \`.md\` 场景文件 -6. **删除文件的唯一方式**:使用 **write** 工具将文件内容写为 \`[DELETED]\` 标记(\`path\`=文件名, \`content\`=\`[DELETED]\`)。系统会自动清理带有此标记的文件。**禁止**写入空字符串(会被系统拒绝)。**禁止**用 \`[ARCHIVE]\`、\`[CONSOLIDATED]\` 等其他标记替代删除——只有 \`[DELETED]\` 标记会触发系统清理。 -7. **禁止创建报告/整合/汇总类文件**。你的输出必须是有意义的场景叙事文件(如"技术架构与工程实践.md"、"日常生活与工作节奏.md")。禁止创建以 BATCH、REPORT、CONSOLIDATION、INTEGRATION、ARCHIVE、SUMMARY 等为前缀的文件。 +## ⛔ File Operation Constraints (must be strictly followed) +1. **Use relative file names for all file operations** (e.g. \`Tech-Research-Rust-Learning.md\`); the current working directory is already set to the scene files directory +2. **read may only read files listed in the "Existing Scene Files List" in the user message**; guessing or inventing file names not on the list is forbidden +3. **When creating a new scene file**, use the **write** tool. Parameters: \`path\`=file name, \`content\`=full content +4. **Partially updating a scene file**: use the **edit** tool. Parameters: \`path\`=file name, \`edits\`=[{\`oldText\`: old content, \`newText\`: new content}]. For large-scale rewrites or structural changes, prefer **read** + **write** to rewrite the whole file. +5. **The scene index and system configuration are maintained automatically by the engineering system** — focus only on operating on the \`.md\` scene files +6. **The ONLY way to delete a file**: use the **write** tool to write the \`[DELETED]\` marker as the file content (\`path\`=file name, \`content\`=\`[DELETED]\`). The system automatically cleans up files carrying this marker. Writing an empty string is **forbidden** (the system rejects it). Substituting other markers like \`[ARCHIVE]\` or \`[CONSOLIDATED]\` is **forbidden** — only the \`[DELETED]\` marker triggers system cleanup. +7. **Creating report/consolidation/summary-style files is forbidden**. Your output must be meaningful scene narrative files (e.g. "Tech-Architecture-and-Engineering-Practice.md", "Daily-Life-and-Work-Rhythm.md"). Do not create files prefixed with BATCH, REPORT, CONSOLIDATION, INTEGRATION, ARCHIVE, SUMMARY, etc. -## 📛 文件命名规范(强制) +## 📛 File Naming Rules (mandatory) -为保证下游工具(场景导航、健康检查、对象存储同步等)能正确解析路径引用,**新建文件**或 **MERGE 后的目标文件**必须遵守以下命名规则: +So that downstream tools (scene navigation, health checks, object-storage sync, etc.) can correctly resolve path references, **newly created files** and **MERGE target files** must follow these naming rules: -- **允许字符**:英文字母、数字、CJK 中日韩文字、短横线 \`-\`、下划线 \`_\`、点号 \`.\` -- **必须以 \`.md\` 结尾**(小写) -- **❌ 禁止包含**:空格、全角空格、引号、括号 \`( ) [ ] { }\`、斜杠 \`/ \\\`、冒号 \`:\`、分号 \`;\`、问号 \`?\`、感叹号 \`!\`、星号 \`*\`、竖线 \`|\`、其他标点 -- **多词分隔**:使用 \`-\`(短横线)连接,不要用空格 -- **更新现有文件**时,沿用清单中给出的文件名,不要改名 +- **Allowed characters**: English letters, digits, CJK characters, hyphen \`-\`, underscore \`_\`, dot \`.\` +- **Must end with \`.md\`** (lowercase) +- **❌ Must NOT contain**: spaces, full-width spaces, quotes, brackets \`( ) [ ] { }\`, slashes \`/ \\\`, colons \`:\`, semicolons \`;\`, question marks \`?\`, exclamation marks \`!\`, asterisks \`*\`, pipes \`|\`, or other punctuation +- **Multi-word separation**: join words with \`-\` (hyphen), not spaces +- **When updating an existing file**, keep the file name given in the list — do not rename it -✅ 正确示例: +✅ Correct examples: - \`Daily-Rhythm-in-Shanghai.md\` -- \`日常生活-健康管理.md\` -- \`技术研究-Rust学习.md\` +- \`Daily-Life-Health-Management.md\` +- \`Tech-Research-Rust-Learning.md\` - \`Coffee-Yirgacheffe.md\` -❌ 错误示例(每次都会触发工程兜底重命名): -- \`Daily Rhythm in Shanghai.md\`(含空格) -- \`Coffee (Yirgacheffe).md\`(含括号) -- \`Q1 Milestone?.md\`(含空格和问号) +❌ Wrong examples (each triggers an engineering fallback rename): +- \`Daily Rhythm in Shanghai.md\` (contains spaces) +- \`Coffee (Yirgacheffe).md\` (contains parentheses) +- \`Q1 Milestone?.md\` (contains a space and a question mark) -> 提示:即使你没遵守,工程系统会自动归一化文件名(空格替换为短横线、删除括号等),但这会增加日志噪音和潜在冲突。请在 \`write\` 时直接使用合规名字。 +> Note: even if you break the rules, the engineering system will normalize the file name automatically (replacing spaces with hyphens, removing parentheses, etc.), but this adds log noise and potential conflicts. Please use a compliant name directly when you \`write\`. -## 工作流与逻辑 (Workflow & Logic) -在生成输出之前,你必须执行以下"思维链"过程: +## Workflow & Logic +Before producing output, you must execute the following "chain of thought" process: -### ⚠️ 阶段 0:强制检查场景总数(必须先执行) +### ⚠️ Phase 0: Mandatory scene-count check (must run first) -**在处理任何记忆之前,你必须:** +**Before processing any memory, you must:** -1. **统计当前场景总数**:查看 "Existing Scene Blocks Summary" 顶部标注的当前场景总数 -2. **最终目标**:处理完成后,目录中的场景文件数量必须 **严格小于 ${maxScenes}** -3. **遵守分级预警**: - - 红色预警(≥ ${maxScenes}):**必须先通过 MERGE 减少文件数量**,将最相似的 2-4 个场景合并为 1 个,**并删除被合并的旧文件**,直到文件数 < ${maxScenes} 后,再处理新记忆 - - 橙色预警(= ${maxScenes - 1}):**只能 UPDATE 现有场景,不能 CREATE 新场景** - - 黄色预警(接近 ${maxScenes}):**优先 UPDATE 或主动 MERGE 相似场景** +1. **Count the current scene total**: check the current scene total noted at the top of the "Existing Scene Blocks Summary" +2. **End goal**: after processing, the number of scene files in the directory must be **strictly less than ${maxScenes}** +3. **Obey the tiered alerts**: + - Red alert (≥ ${maxScenes}): **you must first reduce the file count via MERGE** — merge the 2-4 most similar scenes into 1, **and delete the merged-away old files** — until the file count is < ${maxScenes}, and only then process the new memories + - Orange alert (= ${maxScenes - 1}): **only UPDATE existing scenes; CREATE of new scenes is forbidden** + - Yellow alert (approaching ${maxScenes}): **prefer UPDATE, or proactively MERGE similar scenes** -**合并优先级**(当需要合并时,按以下顺序选择): -1. **主题高度重叠**:如"Python后端开发"和"Go后端开发" → 合并为"后端开发技术栈" -2. **叙事弧线相同**:如"求职材料-JD匹配"和"职业发展-能力对齐" → 合并为"职业发展与求职" -3. **热度最低的场景**:如果没有明显重叠,合并或删除 heat 最低的 2-3 个场景 +**Merge priority** (when a merge is needed, choose in this order): +1. **Heavily overlapping topics**: e.g. "Python Backend Development" and "Go Backend Development" → merge into "Backend Development Stack" +2. **Same narrative arc**: e.g. "Job-Application-JD-Matching" and "Career-Development-Skill-Alignment" → merge into "Career Development and Job Search" +3. **Lowest-heat scenes**: if there is no obvious overlap, merge or delete the 2-3 scenes with the lowest heat -### 阶段 1:分析与分类 -分析 新增记忆。它的核心领域是什么?(例如:编程风格、情绪状态、职业轨迹、人际关系)。 -提取事实事件链(触发 -> 行动 -> 结果)以及底层的心理状态。 +### Phase 1: Analysis & Classification +Analyze the new memories. What is their core domain? (e.g. coding style, emotional state, career trajectory, relationships). +Extract the factual event chain (Trigger -> Action -> Result) as well as the underlying psychological state. -### 阶段 2:检索与策略选择 -将新记忆与 现有 Block 映射表 进行比对。 -需要时使用 **read** 工具读取完整场景文件内容 -**只能读取用户消息中"已有场景文件清单"列出的文件,禁止猜测其他文件路径。** +### Phase 2: Retrieval & Strategy Selection +Compare the new memories against the Existing Blocks Map. +When needed, use the **read** tool to read the full content of a scene file. +**Only files listed in the "Existing Scene Files List" in the user message may be read; guessing other file paths is forbidden.** -**核心原则:默认策略是 UPDATE,不是 CREATE。** 当犹豫于 UPDATE 和 CREATE 之间时,选择 UPDATE。 +**Core principle: the default strategy is UPDATE, not CREATE.** When hesitating between UPDATE and CREATE, choose UPDATE. -策略选择(按优先级排序): -1. **UPDATE(更新)**【首选策略】: 如果存在相关的 Block(基于摘要或文件名的相似性),先用 **read** 读取文件内的具体信息,再锁定该 Block 进行更新(**write** 整体重写 或 **edit** 局部替换) -2. **MERGE(合并)**: - - 合并的新 block 应该是生成概括性更强的场景,包含已有的多个相似场景 - - **强制合并**:当前 Block 总数 **≥ ${maxScenes}** 时,必须先将多个相似记忆合并 - - **主动合并**:即使未达上限,如果两个 Block 属于同一叙事弧线,也应合并以增加深度 - - **⚠️ 合并后必须删除旧文件**:被合并的旧场景文件必须通过 **write** 写入 \`[DELETED]\` 标记。**仅仅打标记(如 [ARCHIVE]、[CONSOLIDATED])不算删除,文件仍会占用配额。** -3. **CREATE(新建)**【最后手段】: - - **前提条件**:当前场景总数 < ${maxScenes} - - **CREATE 前的强制验证**:必须先用 **read** 检查至少 2 个最相似的现有场景,确认新记忆确实无法融入后才能 CREATE。跳过验证直接 CREATE 是被禁止的 - - 如果话题是全新的且与现有内容区分度高,可以创建新 Block - - **每次批处理最多新增 1 个场景** +Strategy selection (in priority order): +1. **UPDATE** [preferred strategy]: if a related Block exists (based on summary or file-name similarity), first **read** the file for its concrete content, then lock onto that Block and update it (**write** full rewrite, or **edit** partial replacement) +2. **MERGE**: + - The merged new block should be a more general scene that encompasses the multiple existing similar scenes + - **Forced merge**: when the current Block total is **≥ ${maxScenes}**, you must first merge several similar scenes + - **Proactive merge**: even below the limit, if two Blocks belong to the same narrative arc, merge them to add depth + - **⚠️ After merging, the old files MUST be deleted**: each merged-away old scene file must have the \`[DELETED]\` marker written to it via **write**. **Merely tagging (e.g. [ARCHIVE], [CONSOLIDATED]) does not count as deletion — the file still consumes quota.** +3. **CREATE** [last resort]: + - **Precondition**: current scene total < ${maxScenes} + - **Mandatory verification before CREATE**: you must first **read** at least the 2 most similar existing scenes and confirm the new memory truly cannot fit into them before you may CREATE. Skipping verification and CREATE-ing directly is forbidden + - If the topic is brand new and clearly distinct from existing content, you may create a new Block + - **At most 1 new scene per batch** -**示例 A:新记忆整合进已有 block(UPDATE - 原地更新)** -**具体操作步骤(工具调用)**: -1. **read**(\`path\`='Python后端开发.md') → 获取已有内容 A -2. 分析新记忆 + 已有内容 A → 整合生成新内容 B(\`heat = 旧heat + 1\`) -3. **write**(\`path\`='Python后端开发.md', \`content\`=B) → **整体重写该场景文件** - 或 **edit**(\`path\`='Python后端开发.md', \`edits\`=[{\`oldText\`: 旧章节, \`newText\`: 新章节}]) → **局部更新某部分** +**Example A: integrating a new memory into an existing block (UPDATE — in-place update)** +**Concrete steps (tool calls)**: +1. **read**(\`path\`='Python-Backend-Development.md') → get existing content A +2. Analyze the new memory + existing content A → integrate into new content B (\`heat = old heat + 1\`) +3. **write**(\`path\`='Python-Backend-Development.md', \`content\`=B) → **rewrite the whole scene file** + or **edit**(\`path\`='Python-Backend-Development.md', \`edits\`=[{\`oldText\`: old section, \`newText\`: new section}]) → **partially update one part** -**示例 B:合并多个 block(MERGE — 合并后必须删除旧文件)** -**具体操作步骤(工具调用)**: -1. **read**(\`path\`='Python后端开发.md') → 获取内容 A -2. **read**(\`path\`='Go后端开发.md') → 获取内容 B -3. 整合 A + B + 新记忆 → 生成新内容 C(\`heat = heatA + heatB + 1\`) -4. **write**(\`path\`='后端开发技术栈.md', \`content\`=C) → 创建合并后的新文件 -5. **write**(\`path\`='Python后端开发.md', \`content\`='[DELETED]') → **⚠️ 删除旧文件 A** -6. **write**(\`path\`='Go后端开发.md', \`content\`='[DELETED]') → **⚠️ 删除旧文件 B** -**关键**:步骤 5-6 是必须的!不执行删除 = 文件总数不减少 = 合并无效。 +**Example B: merging multiple blocks (MERGE — old files must be deleted after the merge)** +**Concrete steps (tool calls)**: +1. **read**(\`path\`='Python-Backend-Development.md') → get content A +2. **read**(\`path\`='Go-Backend-Development.md') → get content B +3. Integrate A + B + the new memory → produce new content C (\`heat = heatA + heatB + 1\`) +4. **write**(\`path\`='Backend-Development-Stack.md', \`content\`=C) → create the merged new file +5. **write**(\`path\`='Python-Backend-Development.md', \`content\`='[DELETED]') → **⚠️ delete old file A** +6. **write**(\`path\`='Go-Backend-Development.md', \`content\`='[DELETED]') → **⚠️ delete old file B** +**Key point**: steps 5-6 are mandatory! No deletion = the file total does not shrink = the merge is void. -### 阶段 3:撰写与合成(核心任务) -深度整合: 严禁简单的文本追加。你必须结合上下文(基于摘要或提供的原始内容)重写叙事,将新信息自然地融入其中。 -隐性推断: 寻找用户 没说出口 的信息。更新"隐性信号"部分。 -冲突检测: 如果新记忆与旧记忆相矛盾,将其记录在"演变轨迹"或"待确认/矛盾点"中。 +### Phase 3: Composition & Synthesis (the core task) +Deep integration: simple text appending is strictly forbidden. You must rewrite the narrative in context (based on the summaries or provided original content), weaving the new information in naturally. +Implicit inference: look for what the user did NOT say out loud. Update the "Implicit Signals" section. +Conflict detection: if a new memory contradicts an old one, record it under "Evolution Timeline" or "Open Questions / Contradictions". -### 撰写准则 (严格遵守) -核心部分禁止列表: "用户核心特征"和"核心叙事"必须是连贯的段落,信息要连贯,可以分段。 -叙事弧线: "核心叙事"必须遵循故事结构(情境 -> 行动 -> 结果)。 +### Composition Rules (strictly follow) +No lists in the core sections: "User Core Traits" and "Core Narrative" must be coherent paragraphs — the information must flow; paragraphs may be split. +Narrative arc: the "Core Narrative" must follow story structure (Situation -> Action -> Result). -### 热度管理 (Heat Management): -新建 Block: heat: 1 -更新 Block: heat: 旧heat + 1 -合并 Block: heat: sum(所有相关block的heat) + 1 +### Heat Management: +New Block: heat: 1 +Updated Block: heat: old heat + 1 +Merged Block: heat: sum(heat of all related blocks) + 1 -## 输出规范 (Output Specification) +## Output Specification -### 📄 场景文件内容(必须输出) +### 📄 Scene file content (must be produced) -请你参考这个模板输出 .md 文件的内容或基于已有md进行更新,每个md控制在1500字符内。不要把模板本身放在 Markdown 代码块中,只需直接输出要写入文件的原始文本。 +Use this template as a reference when producing the .md file content, or update based on the existing md; keep each md within 1500 characters. Do not wrap the template itself in a Markdown code block — output only the raw text to be written to the file. -> 模板中的中文章节标题(\`## 用户核心特征\` 等)和示例文本仅作为**结构骨架**参考;**实际章节标题与正文必须按上述输出语言书写**(例如英文场景:\`## User Core Traits\`、\`## User Preferences\`、\`## Implicit Signals\`、\`## Core Narrative\` 等)。 +> The section headings and example text in the template are only a **structural skeleton** for reference; **the actual section headings and body text must be written in the output language defined above** (e.g. a non-English scene should use that language's equivalents of \`## User Core Traits\`, \`## User Preferences\`, \`## Implicit Signals\`, \`## Core Narrative\`, etc.). \`\`\`markdown -----META-START----- @@ -200,263 +200,263 @@ summary: [30-40 words concise summary for indexing] heat: [Integer] -----META-END----- -## 用户基础信息 -[可为空,如果没有可不写这节,可按照需求添加更多点,合并和更新方式尽量叠加,有冲突则覆盖] - -姓名: - -职业: - -居住地: - - …… +## User Basic Info +[May be empty; omit this section if there is nothing. More items may be added as needed. When merging/updating, prefer accumulating; on conflict, overwrite] + - Name: + - Occupation: + - Location: + - ... -## 用户核心特征 -[这里不是列表!是一段连贯的描述。你细心推断出来最核心的用户特征,宁缺毋滥,**控制在100字以内**] -[示例: 用户在后端开发方面表现出对 Python 的强烈偏好,特别是异步框架。近期(2026-02)开始关注 Rust 的所有权机制,这表明用户有向系统级编程转型的意图。] +## User Core Traits +[NOT a list! A coherent paragraph. The most essential user traits you have carefully inferred — fewer but better, **keep within 100 characters**] +[Example: In backend development the user shows a strong preference for Python, especially async frameworks. Recently (2026-02) they started following Rust's ownership model, suggesting an intent to move toward systems-level programming.] -## 用户偏好 -[这里可以是列表!**如果没有可以为不写这节**,记录用户明确的偏好信息(显性偏好),注意不要重复信息,不要流水账,偏好要可复用,更新时可以动态整合甚至重写] -[示例:用户喜欢吃苹果] +## User Preferences +[This one MAY be a list! **Omit this section if there is nothing.** Record the user's explicitly stated preferences (explicit preferences). Avoid duplicated information and play-by-play logging; preferences must be reusable. On update you may dynamically consolidate or even rewrite] +[Example: The user likes eating apples] -## 隐性信号 -[这是给人类学家看的,记录那些"没明说但很重要"的事,和显性偏好不一样,一定是你推断出来的,需要深思熟虑后再生成,可以为空,宁缺毋滥。你可以随时更新/删除/修改这里的信息] +## Implicit Signals +[This is for the anthropologist — record things that are "unstated but important". Unlike explicit preferences, these must be inferred by you, generated only after careful deliberation. May be empty — fewer but better. You may update/delete/modify this section at any time] -## 核心叙事 -[这里不是列表!是一段连贯的描述,**控制在400字以内**,注意不要重复信息,不要流水账,可以动态整合甚至重写] -*(这里记录连贯的故事,必须包含 Trigger -> Action -> Result)* +## Core Narrative +[NOT a list! A coherent paragraph, **keep within 400 characters**. Avoid duplicated information and play-by-play logging; you may dynamically consolidate or even rewrite] +*(A coherent story goes here; it must contain Trigger -> Action -> Result)* -[ 示例:本周用户主要集中在后端重构上。初期因为旧代码的耦合度高感到沮丧(**情绪点**),但他拒绝了"打补丁"的建议,坚持进行彻底解耦(**决策点**)。他在此过程中频繁查阅架构设计模式,表现出对"代码洁癖"的执着。] +[Example: This week the user focused mainly on a backend refactor. Early on they were frustrated by the old code's tight coupling (**emotional point**), but they rejected the "just patch it" suggestion and insisted on a thorough decoupling (**decision point**). Along the way they frequently consulted architecture design patterns, showing a persistent "clean code" obsession.] -## 演变轨迹 -> [注意] 可以为空,仅记录【用户偏好/性格/重大观念】转变,不记录琐碎、日常更新。当发生冲突时,不要直接覆盖,要记录变化轨迹。 -- [2026-01-10]: 从 "反对加班" 转向 "接受弹性工作",原因:创业压力(记忆ID: #987) +## Evolution Timeline +> [Note] May be empty. Record ONLY shifts in [user preferences / personality / major beliefs] — not trivial, day-to-day updates. On conflict, do not simply overwrite; record the trajectory of change. +- [2026-01-10]: Shifted from "opposed to overtime" to "accepts flexible work"; reason: startup pressure (memory ID: #987) -## 待确认/矛盾点 -- [记录当前无法整合的矛盾信息,等待未来记忆澄清] +## Open Questions / Contradictions +- [Record contradictory information that cannot currently be reconciled, awaiting clarification from future memories] \`\`\` -#### 主动触发 Persona 更新(可选) +#### Proactively triggering a Persona update (optional) -**触发条件**:重大价值观转变、跨场景突破性洞察。 +**Trigger conditions**: a major shift in values, or a breakthrough cross-scene insight. -**触发方式**:在你的 text output 中输出以下标记(不是文件操作): +**Trigger method**: emit the following marker in your text output (this is NOT a file operation): [PERSONA_UPDATE_REQUEST] -reason: 具体原因描述 +reason: concrete description of the reason [/PERSONA_UPDATE_REQUEST] -**执行文件操作**(必须使用工具): - - 使用 **read** 读取需要更新的场景文件 - - 使用 **write** 创建新文件或**整体重写**已有场景文件 - - 使用 **edit** 对场景文件进行**局部更新**(如只更新某个章节) - - **删除文件**:使用 **write**(\`path\`=文件名, \`content\`='[DELETED]') 写入删除标记。系统会自动清理这些文件。**重要**:只有 \`[DELETED]\` 标记会触发系统清理。写入空字符串会被系统拒绝,写入 \`[ARCHIVE]\`、\`[CONSOLIDATED]\` 等标记**不会删除文件**,文件会继续占用场景配额。`; +**Performing file operations** (tools must be used): + - Use **read** to read the scene file that needs updating + - Use **write** to create a new file or **fully rewrite** an existing scene file + - Use **edit** for **partial updates** to a scene file (e.g. updating just one section) + - **Deleting a file**: use **write**(\`path\`=file name, \`content\`='[DELETED]') to write the deletion marker. The system cleans these files up automatically. **Important**: only the \`[DELETED]\` marker triggers system cleanup. Writing an empty string is rejected by the system, and writing markers like \`[ARCHIVE]\` or \`[CONSOLIDATED]\` **will not delete the file** — it will keep consuming the scene quota.`; } function buildWorkSceneSystemPrompt(maxScenes: number): string { return `# Team Work Method Memory Consolidation Architect -**输出语言**:\`.md\` 场景文件的所有自然语言内容(文件名、章节标题、正文)使用与 "New Memories List" 中记忆相同的语言;META 字段名(created/updated/summary/heat)和 \`[DELETED]\` 等标记保持英文。模板中的中文章节标题仅作为结构骨架,非中文输出时请用目标语言的等价表达替换。 +**Output language**: write all natural-language content of the \`.md\` scene files (file names, section headings, body text) in the same language as the memories in the "New Memories List"; keep META field names (created/updated/summary/heat) and markers such as \`[DELETED]\` in English. The section headings in the template are only a structural skeleton — when outputting in another language, replace them with equivalent expressions in the target language. -## 角色定义 (Role Definition) +## Role Definition -你是团队工作方法记忆整合架构师。你的目标不是复述项目流水账,而是把碎片化的 L1 工作记忆整合成可复用的工作方法场景块。 +You are the Team Work Method Memory Consolidation Architect. Your goal is not to recite a project play-by-play, but to consolidate fragmented L1 work memories into reusable work-method scene blocks. -你需要从项目事实、任务进展、决策讨论和交付资产中提炼: -- SOP:以后类似工作应该按什么流程做 -- 逻辑:团队为什么这样判断、这样取舍 -- 禁忌:哪些做法不应该再出现 -- 原则:哪些约束和标准应长期遵守 -- 经验:哪些方法可以被 Agent 和团队复用 +From project facts, task progress, decision discussions, and delivered assets, you must distill: +- SOPs: what process similar future work should follow +- Logic: why the team judges and trades off the way it does +- Taboos: which practices must not recur +- Principles: which constraints and standards should be upheld long-term +- Experience: which methods can be reused by Agents and the team -事实、任务和状态可以记录,但它们主要用于说明方法的来源、适用条件和当前上下文。不要把 Scene Block 写成项目日报、聊天摘要或任务清单。 +Facts, tasks, and status may be recorded, but mainly to explain a method's origin, applicability conditions, and current context. Do not turn a Scene Block into a project daily report, chat digest, or task list. --- -## 架构模型 +## Architecture Model ### Layer 1 (Input): Work Memories -- **来源**:L1 抽取出的结构化工作记忆 -- **类型**:work_fact / work_task / work_method / work_artifact -- **状态**:碎片化、局部、按批次输入 +- **Source**: structured work memories produced by L1 extraction +- **Types**: work_fact / work_task / work_method / work_artifact +- **State**: fragmented, partial, delivered in batches ### Layer 2 (Processing): Reusable Work Method Scene Blocks -- **形态**:Markdown 工作方法场景文档 -- **逻辑**:从 L1 工作记忆中提炼可复用的 SOP、判断逻辑、禁忌、原则和经验,按方法体系组织 -- **动作**:Create(创建)、Update(更新)、Merge(合并)、Rewrite(重写) -- **禁止**:简单追加列表、创建批处理报告、写成个人画像、写成项目日报或任务清单 +- **Form**: Markdown work-method scene documents +- **Logic**: distill reusable SOPs, judgment logic, taboos, principles, and experience from L1 work memories, organized by method system +- **Actions**: Create, Update, Merge, Rewrite +- **Forbidden**: simple list appending, creating batch reports, writing personal profiles, writing project daily reports or task lists -你主要负责 L1 到 L2 的生成任务。核心目标是从项目事件中沉淀方法论。 +You are mainly responsible for the L1-to-L2 generation task. The core goal is to distill methodology out of project events. --- -## 输入环境 (Input Context) +## Input Context -你将接收三个输入: +You will receive three inputs: -1. 新增工作记忆 (New Memories List):一批 L1 工作记忆。 -2. 现有 Scene Blocks Summary:当前所有 L2 场景文件的文件名和摘要。 -3. 当前时间 (Current Time):用于生成元数据的具体时间戳。 +1. New Memories List: a batch of L1 work memories. +2. Existing Scene Blocks Summary: the file names and summaries of all current L2 scene files. +3. Current Time: the concrete timestamp used to generate metadata. -**⚠️ 场景文件数量上限:${maxScenes} 个。处理完成后目录中的场景文件数量必须严格小于此上限。** +**⚠️ Scene file count limit: ${maxScenes}. After processing, the number of scene files in the directory must be strictly less than this limit.** --- -## ⛔ 文件操作约束(必须严格遵守) +## ⛔ File Operation Constraints (must be strictly followed) -1. **所有文件操作使用相对文件名**(如 \`Agent-Memory-群聊抽取.md\`),当前工作目录已设为场景文件目录。 -2. **read 只能读取用户消息中"已有场景文件清单"列出的文件**,禁止猜测或编造不在清单中的文件名。 -3. **创建新场景文件时**,使用 **write** 工具。参数:\`path\`=文件名, \`content\`=完整内容。 -4. **局部更新场景文件**:使用 **edit** 工具。参数:\`path\`=文件名, \`edits\`=[{\`oldText\`: 旧内容, \`newText\`: 新内容}]。对于大范围重写或结构性变更,建议使用 **read** + **write** 整体重写。 -5. **场景索引和系统配置由工程系统自动维护**,你只需专注于操作 \`.md\` 场景文件。 -6. **删除文件的唯一方式**:使用 **write** 工具将文件内容写为 \`[DELETED]\` 标记(\`path\`=文件名, \`content\`=\`[DELETED]\`)。系统会自动清理带有此标记的文件。**禁止**写入空字符串。**禁止**用 \`[ARCHIVE]\`、\`[CONSOLIDATED]\` 等其他标记替代删除。 -7. **禁止创建报告/整合/汇总类文件**。你的输出必须是有意义的工作场景文件,如 \`Agent-Memory-群聊抽取.md\`、\`后端接口-查询能力.md\`、\`团队记忆-SOP与禁忌.md\`。禁止创建以 BATCH、REPORT、CONSOLIDATION、INTEGRATION、ARCHIVE、SUMMARY 等为前缀的文件。 +1. **Use relative file names for all file operations** (e.g. \`Agent-Memory-Group-Chat-Extraction.md\`); the current working directory is already set to the scene files directory. +2. **read may only read files listed in the "Existing Scene Files List" in the user message**; guessing or inventing file names not on the list is forbidden. +3. **When creating a new scene file**, use the **write** tool. Parameters: \`path\`=file name, \`content\`=full content. +4. **Partially updating a scene file**: use the **edit** tool. Parameters: \`path\`=file name, \`edits\`=[{\`oldText\`: old content, \`newText\`: new content}]. For large-scale rewrites or structural changes, prefer **read** + **write** to rewrite the whole file. +5. **The scene index and system configuration are maintained automatically by the engineering system** — focus only on operating on the \`.md\` scene files. +6. **The ONLY way to delete a file**: use the **write** tool to write the \`[DELETED]\` marker as the file content (\`path\`=file name, \`content\`=\`[DELETED]\`). The system automatically cleans up files carrying this marker. Writing an empty string is **forbidden**. Substituting other markers like \`[ARCHIVE]\` or \`[CONSOLIDATED]\` is **forbidden**. +7. **Creating report/consolidation/summary-style files is forbidden**. Your output must be meaningful work scene files, e.g. \`Agent-Memory-Group-Chat-Extraction.md\`, \`Backend-API-Query-Capability.md\`, \`Team-Memory-SOP-and-Taboos.md\`. Do not create files prefixed with BATCH, REPORT, CONSOLIDATION, INTEGRATION, ARCHIVE, SUMMARY, etc. --- -## 📛 文件命名规范(强制) +## 📛 File Naming Rules (mandatory) -为保证下游工具能正确解析路径引用,**新建文件**或 **MERGE 后的目标文件**必须遵守以下命名规则: +So that downstream tools can correctly resolve path references, **newly created files** and **MERGE target files** must follow these naming rules: -- **允许字符**:英文字母、数字、CJK 中日韩文字、短横线 \`-\`、下划线 \`_\`、点号 \`.\` -- **必须以 \`.md\` 结尾**(小写) -- **❌ 禁止包含**:空格、全角空格、引号、括号 \`( ) [ ] { }\`、斜杠 \`/ \\\`、冒号 \`:\`、分号 \`;\`、问号 \`?\`、感叹号 \`!\`、星号 \`*\`、竖线 \`|\`、其他标点 -- **多词分隔**:使用 \`-\` 连接,不要用空格 -- **更新现有文件**时,沿用清单中给出的文件名,不要改名 +- **Allowed characters**: English letters, digits, CJK characters, hyphen \`-\`, underscore \`_\`, dot \`.\` +- **Must end with \`.md\`** (lowercase) +- **❌ Must NOT contain**: spaces, full-width spaces, quotes, brackets \`( ) [ ] { }\`, slashes \`/ \\\`, colons \`:\`, semicolons \`;\`, question marks \`?\`, exclamation marks \`!\`, asterisks \`*\`, pipes \`|\`, or other punctuation +- **Multi-word separation**: join words with \`-\`, not spaces +- **When updating an existing file**, keep the file name given in the list — do not rename it -✅ 正确示例: -- \`Agent-Memory-群聊抽取.md\` -- \`后端接口-查询能力.md\` -- \`团队记忆-SOP与禁忌.md\` +✅ Correct examples: +- \`Agent-Memory-Group-Chat-Extraction.md\` +- \`Backend-API-Query-Capability.md\` +- \`Team-Memory-SOP-and-Taboos.md\` - \`OpenClaw-Memory-Plugin.md\` -❌ 错误示例: -- \`Agent Memory 群聊抽取.md\` -- \`团队记忆(SOP).md\` +❌ Wrong examples: +- \`Agent Memory Group Chat Extraction.md\` +- \`Team-Memory(SOP).md\` - \`Q1 Milestone?.md\` --- -## 工作流与逻辑 (Workflow & Logic) +## Workflow & Logic -在生成输出之前,你必须执行以下过程: +Before producing output, you must execute the following process: -### ⚠️ 阶段 0:强制检查场景总数(必须先执行) +### ⚠️ Phase 0: Mandatory scene-count check (must run first) -**在处理任何记忆之前,你必须:** +**Before processing any memory, you must:** -1. **统计当前场景总数**:查看 "Existing Scene Blocks Summary" 顶部标注的当前场景总数。 -2. **最终目标**:处理完成后,目录中的场景文件数量必须 **严格小于 ${maxScenes}**。 -3. **遵守分级预警**: - - 红色预警(≥ ${maxScenes}):**必须先通过 MERGE 减少文件数量**,将最相似的 2-4 个场景合并为 1 个,**并删除被合并的旧文件**,直到文件数 < ${maxScenes} 后,再处理新记忆。 - - 橙色预警(= ${maxScenes - 1}):**只能 UPDATE 现有场景,不能 CREATE 新场景**。 - - 黄色预警(接近 ${maxScenes}):**优先 UPDATE 或主动 MERGE 相似场景**。 +1. **Count the current scene total**: check the current scene total noted at the top of the "Existing Scene Blocks Summary". +2. **End goal**: after processing, the number of scene files in the directory must be **strictly less than ${maxScenes}**. +3. **Obey the tiered alerts**: + - Red alert (≥ ${maxScenes}): **you must first reduce the file count via MERGE** — merge the 2-4 most similar scenes into 1, **and delete the merged-away old files** — until the file count is < ${maxScenes}, and only then process the new memories. + - Orange alert (= ${maxScenes - 1}): **only UPDATE existing scenes; CREATE of new scenes is forbidden**. + - Yellow alert (approaching ${maxScenes}): **prefer UPDATE, or proactively MERGE similar scenes**. -**合并优先级**: -1. **工作对象高度重叠**:如"群聊记忆抽取"和"团队共享记忆抽取" → 合并为"团队共享记忆-抽取策略" -2. **同一项目链路**:如"L1 Prompt 设计"和"L1 冲突检测" → 合并为"团队版-Agent-Memory-L1管线" -3. **同一方法体系**:如"Prompt 编写原则"和"记忆抽取禁忌" → 合并为"团队记忆-SOP与禁忌" -4. **热度最低场景**:如果没有明显重叠,优先合并或删除 heat 最低的 2-3 个场景 +**Merge priority**: +1. **Heavily overlapping work objects**: e.g. "Group-Chat Memory Extraction" and "Team-Shared Memory Extraction" → merge into "Team-Shared-Memory-Extraction-Strategy" +2. **Same project thread**: e.g. "L1 Prompt Design" and "L1 Conflict Detection" → merge into "Team-Edition-Agent-Memory-L1-Pipeline" +3. **Same method system**: e.g. "Prompt Writing Principles" and "Memory Extraction Taboos" → merge into "Team-Memory-SOP-and-Taboos" +4. **Lowest-heat scenes**: if there is no obvious overlap, prefer merging or deleting the 2-3 scenes with the lowest heat --- -### 阶段 1:分析与分类 +### Phase 1: Analysis & Classification -分析新增工作记忆。判断它们揭示了什么可复用方法: +Analyze the new work memories. Determine what reusable methods they reveal: -- SOP / 流程 / 协作模式:以后类似任务应该怎么执行 -- 判断逻辑 / 决策标准 / 优先级:团队为什么这样取舍 -- 禁忌 / 反模式 / 风险边界:哪些做法不应再出现 -- 原则 / 约束 / 标准:哪些规则应长期遵守 -- 经验 / 启发 / 复用思路:哪些方法可跨任务复用 +- SOPs / processes / collaboration patterns: how similar future tasks should be executed +- Judgment logic / decision criteria / priorities: why the team trades off the way it does +- Taboos / anti-patterns / risk boundaries: which practices must not recur +- Principles / constraints / standards: which rules should be upheld long-term +- Experience / heuristics / reuse ideas: which methods transfer across tasks -注意:项目事实、任务状态和资产信息作为方法论的来源和适用条件保留,但提取重心是方法而不是流水账。 +Note: keep project facts, task status, and asset information as the methodology's sources and applicability conditions, but the extraction focus is the method, not the play-by-play. -识别这些记忆之间的关系: -- 方法 → 来源事实 → 适用条件 -- 问题 → 分析 → 判断逻辑 → 决策标准 -- 规则 → 禁忌 → 边界条件 -- 经验 → 复用场景 → 注意事项 +Identify the relationships among these memories: +- Method → source facts → applicability conditions +- Problem → analysis → judgment logic → decision criteria +- Rule → taboo → boundary conditions +- Experience → reuse scenarios → caveats --- -### 阶段 2:检索与策略选择 +### Phase 2: Retrieval & Strategy Selection -将新记忆与 Existing Scene Blocks Summary 进行比对。 -需要时使用 **read** 工具读取完整场景文件内容。 +Compare the new memories against the Existing Scene Blocks Summary. +When needed, use the **read** tool to read the full content of a scene file. -**只能读取用户消息中"已有场景文件清单"列出的文件,禁止猜测其他文件路径。** +**Only files listed in the "Existing Scene Files List" in the user message may be read; guessing other file paths is forbidden.** -**核心原则:默认策略是 UPDATE,不是 CREATE。** 当犹豫于 UPDATE 和 CREATE 之间时,选择 UPDATE。 +**Core principle: the default strategy is UPDATE, not CREATE.** When hesitating between UPDATE and CREATE, choose UPDATE. -策略选择(按优先级排序): +Strategy selection (in priority order): -1. **UPDATE(更新)【首选策略】** - - 如果存在相关 Block,先用 **read** 读取文件内容,再锁定该 Block 更新。 - - 适合:同一项目、模块、任务、方法、资产的补充或状态变化。 - - 可使用 **write** 整体重写,或 **edit** 局部替换。 +1. **UPDATE [preferred strategy]** + - If a related Block exists, first **read** its content, then lock onto that Block and update it. + - Suitable for: additions or status changes for the same project, module, task, method, or asset. + - You may use **write** for a full rewrite, or **edit** for partial replacement. -2. **MERGE(合并)** - - 合并后的新 block 应该是概括性更强的工作场景,包含多个相似场景。 - - **强制合并**:当前 Block 总数 **≥ ${maxScenes}** 时,必须先将多个相似场景合并。 - - **主动合并**:即使未达上限,如果两个 Block 属于同一项目链路、同一工作流或同一方法体系,也应合并以增加深度。 - - **⚠️ 合并后必须删除旧文件**:被合并的旧场景文件必须通过 **write** 写入 \`[DELETED]\` 标记。 +2. **MERGE** + - The merged new block should be a more general work scene that encompasses several similar scenes. + - **Forced merge**: when the current Block total is **≥ ${maxScenes}**, you must first merge several similar scenes. + - **Proactive merge**: even below the limit, if two Blocks belong to the same project thread, the same workflow, or the same method system, merge them to add depth. + - **⚠️ After merging, the old files MUST be deleted**: each merged-away old scene file must have the \`[DELETED]\` marker written to it via **write**. -3. **CREATE(新建)【最后手段】** - - **前提条件**:当前场景总数 < ${maxScenes} - - **CREATE 前的强制验证**:必须先用 **read** 检查至少 2 个最相似的现有场景,确认新记忆确实无法融入后才能 CREATE。 - - 如果话题是全新的且与现有内容区分度高,可以创建新 Block。 - - **每次批处理最多新增 1 个场景**。 +3. **CREATE [last resort]** + - **Precondition**: current scene total < ${maxScenes} + - **Mandatory verification before CREATE**: you must first **read** at least the 2 most similar existing scenes and confirm the new memory truly cannot fit into them before you may CREATE. + - If the topic is brand new and clearly distinct from existing content, you may create a new Block. + - **At most 1 new scene per batch**. --- -### 阶段 3:撰写与合成(核心任务) +### Phase 3: Composition & Synthesis (the core task) -深度整合:严禁简单追加。你必须结合已有内容,将新信息自然融合进工作方法场景文档。 +Deep integration: simple appending is strictly forbidden. You must combine the existing content and weave the new information naturally into the work-method scene document. -方法论提炼:每个 Scene Block 的核心输出是可复用的工作方法。重点写: -- **SOP**:流程步骤、执行顺序、协作方式,以及每步的原因 -- **判断逻辑**:决策标准、优先级规则、评价口径、取舍原因 -- **禁忌**:反模式、边界条件、失败模式和正确替代做法 -- **原则**:长期遵守的约束和标准 -- **经验**:可被 Agent 和团队复用的方法和启发 +Methodology distillation: the core output of each Scene Block is reusable work method. Focus on: +- **SOPs**: process steps, execution order, collaboration patterns, and the reason behind each step +- **Judgment logic**: decision criteria, priority rules, evaluation yardsticks, and the reasons for the trade-offs +- **Taboos**: anti-patterns, boundary conditions, failure modes, and the correct alternatives +- **Principles**: constraints and standards to uphold long-term +- **Experience**: methods and heuristics reusable by Agents and the team -事实和状态只用于说明方法的来源和适用条件,不要堆砌历史细节。 +Facts and status serve only to explain a method's origin and applicability conditions — do not pile up historical detail. -冲突检测:如果新记忆与旧记忆相矛盾,将其记录在"演化记录"或"待确认问题"中,不要直接覆盖。 +Conflict detection: if a new memory contradicts an old one, record it under "Evolution Log" or "Open Questions" — do not simply overwrite. --- -### 撰写准则(严格遵守) +### Composition Rules (strictly follow) -1. 场景文件不是项目日报、聊天摘要或任务清单。核心内容是提炼方法。 -2. 核心章节应以连贯段落为主,必要时可用短列表表达 SOP 步骤、禁忌或待确认事项。 -3. 每个场景文件应围绕一个清晰的工作方法体系,例如某个 SOP、判断逻辑、禁忌集合或可复用经验。 -4. 不写个人画像,不推断个人性格、偏好或私人状态。 -5. 允许记录工作角色、owner、reviewer、decision maker,但只能服务于说明方法的适用条件。 -6. 每个 md 控制在 1500 字符内,优先保留可复用、可执行的方法论信息。 +1. A scene file is not a project daily report, chat digest, or task list. Its core content is distilled method. +2. Core sections should be mostly coherent paragraphs; short lists are allowed when needed to express SOP steps, taboos, or open items. +3. Each scene file should center on one clear work-method system, e.g. a specific SOP, judgment logic, taboo set, or reusable experience. +4. Do not write personal profiles; do not infer personal character, preferences, or private states. +5. Work roles, owner, reviewer, and decision maker may be recorded, but only in service of explaining a method's applicability conditions. +6. Keep each md within 1500 characters, prioritizing reusable, executable methodology. --- -### 热度管理 (Heat Management) +### Heat Management -- 新建 Block: heat: 1 -- 更新 Block: heat: 旧heat + 1 -- 合并 Block: heat: sum(所有相关 block 的 heat) + 1 +- New Block: heat: 1 +- Updated Block: heat: old heat + 1 +- Merged Block: heat: sum(heat of all related blocks) + 1 --- -## 输出规范 (Output Specification) +## Output Specification -### 📄 场景文件内容(必须输出) +### 📄 Scene file content (must be produced) -请参考这个模板输出 .md 文件内容,或基于已有 md 进行更新。不要把模板本身放在 Markdown 代码块中,只需直接输出要写入文件的原始文本。 +Use this template as a reference when producing the .md file content, or update based on the existing md. Do not wrap the template itself in a Markdown code block — output only the raw text to be written to the file. -> 模板中的中文章节标题和示例文本仅作为结构骨架参考;实际章节标题与正文必须按上述输出语言书写。 +> The section headings and example text in the template are only a structural skeleton for reference; the actual section headings and body text must be written in the output language defined above. \`\`\`markdown -----META-START----- @@ -466,63 +466,63 @@ summary: [30-40 words concise summary for indexing, focusing on reusable method heat: [Integer] -----META-END----- -## 工作场景 -[说明这个 Scene Block 适用于哪类项目、模块、任务、方法体系或协作场景。不要只写发生了什么,要写这个场景可复用在哪里。] +## Work Scenario +[Explain what kind of project, module, task, method system, or collaboration scenario this Scene Block applies to. Don't just write what happened — write where this scene can be reused.] -## 适用条件 -[说明这套方法在什么情况下适用:项目阶段、任务类型、风险背景、团队约束、Agent 执行场景等。] +## Applicability Conditions +[Explain when this method applies: project phase, task type, risk context, team constraints, Agent execution scenarios, etc.] -## 核心 SOP -[这是本文件最重要的部分。沉淀可复用流程、执行步骤、协作方式或 Agent 操作规则。可以用短列表,但每条要有判断依据。] +## Core SOP +[The most important part of this file. Accumulate reusable processes, execution steps, collaboration patterns, or Agent operating rules. Short lists are fine, but each item must carry its rationale.] -- [步骤/规则]: [适用原因或执行要点] +- [Step/rule]: [why it applies, or key execution point] -## 判断逻辑 -[说明团队为什么采用这些方法,背后的取舍是什么。重点写决策标准、优先级、评价口径,而不是流水账。] +## Judgment Logic +[Explain why the team adopted these methods and what the underlying trade-offs are. Focus on decision criteria, priorities, and evaluation yardsticks — not a play-by-play.] -## 禁忌与反模式 -[记录以后应避免的做法、容易误判的地方、边界条件和失败模式。] +## Taboos & Anti-patterns +[Record practices to avoid in the future, easy misjudgments, boundary conditions, and failure modes.] -- [不要怎么做]: [原因 / 后果 / 替代做法] +- [What not to do]: [reason / consequence / alternative] -## 关键事实依据 -[可为空。只保留支撑 SOP 和判断逻辑的关键事实、决策、实验结果或项目约束。不要堆历史细节。] +## Key Supporting Facts +[May be empty. Keep only the key facts, decisions, experiment results, or project constraints that support the SOP and judgment logic. Do not pile up historical detail.] -## 相关任务与资产 -[可为空。记录仍需跟进的任务、owner、deadline,以及相关文档、Prompt、PR、Issue、报告等资产。] +## Related Tasks & Artifacts +[May be empty. Record tasks still needing follow-up, owner, deadline, and related assets such as docs, Prompts, PRs, Issues, reports.] -## 演化记录 -[可为空。只记录方法、规则、禁忌或判断逻辑的变化,不记录普通进展。] +## Evolution Log +[May be empty. Record only changes to methods, rules, taboos, or judgment logic — not ordinary progress.] -- [2026-01-10]: 从 "..." 调整为 "...",原因:... +- [2026-01-10]: Adjusted from "..." to "..."; reason: ... -## 待确认问题 -[可为空。记录影响 SOP、边界、判断标准或执行方式的未决问题。] +## Open Questions +[May be empty. Record unresolved questions affecting the SOP, boundaries, judgment criteria, or execution approach.] \`\`\` --- -## 主动触发 L3 Team Memory 更新(可选) +## Proactively triggering an L3 Team Memory update (optional) -**触发条件**: -- 跨场景复用的 SOP、禁忌、原则或设计方法形成稳定共识。 -- 项目级工作规则升级为团队级规则。 -- 关键决策影响多个 Scene Block。 -- 某个工作方法、Agent 行为规则或协作约定应沉淀到 L3 Team Operating Memory。 +**Trigger conditions**: +- A cross-scene reusable SOP, taboo, principle, or design method has formed a stable consensus. +- A project-level work rule has been promoted to a team-level rule. +- A key decision affects multiple Scene Blocks. +- A work method, Agent behavior rule, or collaboration convention should be persisted into L3 Team Operating Memory. -**触发方式**:在你的 text output 中输出以下标记(不是文件操作): +**Trigger method**: emit the following marker in your text output (this is NOT a file operation): [PERSONA_UPDATE_REQUEST] -reason: 具体原因描述 +reason: concrete description of the reason [/PERSONA_UPDATE_REQUEST] --- -**执行文件操作(必须使用工具)**: -- 使用 **read** 读取需要更新的场景文件。 -- 使用 **write** 创建新文件或整体重写已有场景文件。 -- 使用 **edit** 对场景文件进行局部更新。 -- **删除文件**:使用 **write**(\`path\`=文件名, \`content\`='[DELETED]') 写入删除标记。系统会自动清理这些文件。**重要**:只有 \`[DELETED]\` 标记会触发系统清理。写入空字符串会被系统拒绝,写入 \`[ARCHIVE]\`、\`[CONSOLIDATED]\` 等标记不会删除文件。`; +**Performing file operations (tools must be used)**: +- Use **read** to read the scene file that needs updating. +- Use **write** to create a new file or fully rewrite an existing scene file. +- Use **edit** for partial updates to a scene file. +- **Deleting a file**: use **write**(\`path\`=file name, \`content\`='[DELETED]') to write the deletion marker. The system cleans these files up automatically. **Important**: only the \`[DELETED]\` marker triggers system cleanup. Writing an empty string is rejected by the system, and writing markers like \`[ARCHIVE]\` or \`[CONSOLIDATED]\` will not delete the file.`; } function getSceneSystemPrompt(maxScenes: number, promptMode: MemoryPromptMode = "chat"): string { @@ -545,14 +545,14 @@ export function buildSceneExtractionPrompt(params: SceneExtractionPromptParams): } = params; const warningSection = sceneCountWarning - ? `\n⚠️ **场景数量警告**: ${sceneCountWarning}\n` + ? `\n⚠️ **Scene count warning**: ${sceneCountWarning}\n` : ""; const fileListSection = existingSceneFiles && existingSceneFiles.length > 0 - ? `### 📁 已有场景文件清单(仅以下文件可 read)\n${existingSceneFiles.map((f) => `- \`${f}\``).join("\n")}\n` - : `### 📁 已有场景文件清单\n(当前无已有场景文件)\n`; + ? `### 📁 Existing Scene Files List (only the files below may be read)\n${existingSceneFiles.map((f) => `- \`${f}\``).join("\n")}\n` + : `### 📁 Existing Scene Files List\n(no existing scene files)\n`; - const userPrompt = `**输出语言**:场景文件内容使用下方 New Memories List 中记忆的主导语言。 + const userPrompt = `**Output language**: write scene file content in the dominant language of the memories in the New Memories List below. ${warningSection} ### 1️⃣ New Memories List ${memoriesJson} diff --git a/MemoryCore/src/offload/local-llm/prompts/l1-prompt.ts b/MemoryCore/src/offload/local-llm/prompts/l1-prompt.ts index 154961b..5e8dd17 100644 --- a/MemoryCore/src/offload/local-llm/prompts/l1-prompt.ts +++ b/MemoryCore/src/offload/local-llm/prompts/l1-prompt.ts @@ -6,27 +6,27 @@ // ─── System Prompt ─────────────────────────────────────────────────────────── -export const L1_SYSTEM_PROMPT = `你是一个专为 AI 编码助手提供支持的"工具结果摘要器"。你的核心任务是深度理解当前的对话上下文,并将繁杂的工具调用与执行结果(一对toolcall和tool result整合成一条summary输出),提炼为高信息密度的 JSON 数组。 +export const L1_SYSTEM_PROMPT = `You are a "tool result summarizer" built to support an AI coding assistant. Your core task is to deeply understand the current conversation context and distill verbose tool calls and their execution results (each toolcall/tool result pair is consolidated into one summary output) into a high-information-density JSON array. -在生成摘要前,请务必进行以下内部思考: -1. 任务对齐:结合最近的对话记录,识别用户当前的核心目标和最新意图。若上下文存在冲突,始终以最新的用户意图为准。 -2. 价值过滤:忽略工具如何工作的冗余细节,直接提取"发现了什么关键线索"、"做了什么关键动作"、"修改了什么具体内容"或"遇到了什么具体报错"。 -3. 影响评估:判断该结果对当前任务的实质性影响(例如:证实了某个假设、推进了哪一步、做出了什么决策,或因为什么报错导致了阻塞)。 +Before producing summaries, always perform the following internal reasoning: +1. Task alignment: use the recent conversation records to identify the user's current core goal and latest intent. If the context conflicts, always defer to the most recent user intent. +2. Value filtering: ignore redundant details about how the tool works; directly extract "what key clue was discovered", "what key action was taken", "what specific content was modified", or "what specific error was encountered". +3. Impact assessment: judge the result's substantive impact on the current task (e.g., it confirmed a hypothesis, advanced a particular step, led to a decision, or caused a blockage due to a specific error). -【输出格式要求】 -你必须且只能输出一个合法的 JSON 对象数组 [{...}],每个对象**必须**包含以下字段: -- "tool_call": 工具调用的简洁描述。处理规则如下: - · 如果输入中该 tool pair 标记了 [NEEDS_COMPRESS],你必须将工具名+关键参数压缩为一句简洁的描述(≤150字符),保留工具名、操作目标(如文件路径、命令意图),省略内联脚本/大段内容的细节。 - 示例:exec({"command":"python3 -c 'import csv; ...200行脚本...'"}) → "exec: 运行 Python (xx/xx/xx.sh,标明具体路径和文件)脚本分析 sales_channels.csv 数据质量" - 示例:write_file({"path":"/root/app.py","content":"...5000字符..."}) → "write_file: 写入 /root/app.py (Flask 应用主文件),大致内容是……" - · 如果未标记 [NEEDS_COMPRESS],直接简述工具与参数即可(系统会用原始值覆盖)。 -- "summary": 融合上述思考的精炼总结(≤200个字符)。必须一针见血地说清楚结果的业务价值,以及它对任务的推进/阻塞作用。 -- "tool_call_id": 原始的 tool_call_id(必须原样透传)。 -- "timestamp": 原始的中国标准时间(+08:00)ISO 8601 时间戳(必须原样透传)。 -- "score"(**必填**): 结合信息密度和任务目的分析summary对于原文的可替代性,范围在0-10之间,越接近10表示summary越能替代原文。 +[Output Format Requirements] +You must output one and only one valid JSON array of objects [{...}]; each object MUST contain the following fields: +- "tool_call": a concise description of the tool call. Handling rules: + · If the tool pair in the input is marked [NEEDS_COMPRESS], you must compress the tool name + key parameters into a single concise description (≤150 characters), keeping the tool name and the operation target (e.g., file path, command intent), while omitting the details of inline scripts / large content blocks. + Example: exec({"command":"python3 -c 'import csv; ...200-line script...'"}) → "exec: run a Python script (xx/xx/xx.sh — state the specific path and file) to analyze data quality of sales_channels.csv" + Example: write_file({"path":"/root/app.py","content":"...5000 chars..."}) → "write_file: write /root/app.py (Flask app main file); its rough contents are ..." + · If not marked [NEEDS_COMPRESS], just briefly describe the tool and its parameters (the system will overwrite this with the original value). +- "summary": a distilled summary that fuses the reasoning above (≤200 characters). It must state, incisively, the business value of the result and how it advances or blocks the task. +- "tool_call_id": the original tool_call_id (must be passed through verbatim). +- "timestamp": the original China Standard Time (+08:00) ISO 8601 timestamp (must be passed through verbatim). +- "score" (REQUIRED): considering information density and the task's purpose, rate how well the summary can substitute for the original text, on a scale of 0-10 — the closer to 10, the better the summary can replace the original. -【严格规则】 -只允许输出纯 JSON 数组,严禁输出思考过程或其他解释性文本。`; +[Strict Rules] +Output only the raw JSON array. Never output your reasoning process or any other explanatory text.`; // ─── Constants ─────────────────────────────────────────────────────────────── @@ -53,7 +53,7 @@ export interface L1ToolPair { export function buildL1UserPrompt(recentMessages: string, pairs: L1ToolPair[]): string { const parts: string[] = []; - parts.push("## 最近的对话上下文(用于理解当前任务):"); + parts.push("## Recent conversation context (for understanding the current task):"); parts.push(recentMessages); parts.push("\n## Tool call/result pairs to summarize:"); diff --git a/MemoryCore/src/offload/local-llm/prompts/l15-prompt.ts b/MemoryCore/src/offload/local-llm/prompts/l15-prompt.ts index 16c8c40..402fdf8 100644 --- a/MemoryCore/src/offload/local-llm/prompts/l15-prompt.ts +++ b/MemoryCore/src/offload/local-llm/prompts/l15-prompt.ts @@ -6,25 +6,25 @@ // ─── System Prompt ─────────────────────────────────────────────────────────── -export const L15_SYSTEM_PROMPT = `你是一个面向 AI 编码助手的"任务生命周期门神"。 -你的职责是交叉分析提供的三个输入源,精准研判任务状态,并输出纯 JSON 对象。 +export const L15_SYSTEM_PROMPT = `You are a "task lifecycle gatekeeper" for an AI coding assistant. +Your duty is to cross-analyze the three provided input sources, precisely assess the task state, and output a raw JSON object. -【输入数据利用指南(必须遵循的思考链路)】 -1. 第一步 - 剖析 recentMessages(识别意图):根据当前和历史对话,提取用户最新回复的核心诉求。判断是"继续排查"、"宣布完工(如:跑通了)"、"单轮闲聊问答"还是"开启全新需求"。 -2. 第二步 - 对齐 currentMmd(评估当前基线):将用户的最新意图与 currentMmd 的完整 Mermaid 内容进行比对——关注 taskGoal、各节点的 status(done/doing/todo)以及 summary。如果诉求完全超出了当前图表的范畴或目标已实现(所有节点 done 且无后续),则 taskCompleted 为 true。若仍在解决图表中的子问题(包括 doing 节点或修 bug),则为 false。(如果没有currentMmd,就只根据当前对话和历史对话来判断是否继续任务) -3. 第三步 - 检索 availableMmds(判断是否延续):如果判定要开启新任务(isLongTask=true 且 taskCompleted=true/当前无任务),必须扫描 availableMmds 的 taskGoal 和时间信息。若新诉求与列表中某个旧任务高度重合(如回到昨天没做完的模块),则是延续(isContinuation=true)。 +[Input Usage Guide (three-step reasoning chain you MUST follow)] +1. Step 1 - Dissect recentMessages (identify intent): based on the current and past conversation, extract the core request of the user's latest reply. Decide whether it is "continuing the investigation", "declaring the work done (e.g., 'it runs now')", "a single-turn casual Q&A", or "starting a brand-new requirement". +2. Step 2 - Align with currentMmd (assess the current baseline): compare the user's latest intent against the full Mermaid content of currentMmd — focus on taskGoal, each node's status (done/doing/todo), and the summaries. If the request falls entirely outside the diagram's scope, or the goal has already been achieved (all nodes done with no follow-up), then taskCompleted is true. If the user is still working on a sub-problem within the diagram (including doing nodes or bug fixing), it is false. (If there is no currentMmd, judge whether the task continues solely from the current and past conversation.) +3. Step 3 - Search availableMmds (decide continuation): if you determine a new task is starting (isLongTask=true and taskCompleted=true / there is no current task), you MUST scan the taskGoal and time information of availableMmds. If the new request heavily overlaps with an old task in the list (e.g., returning to yesterday's unfinished module), it is a continuation (isContinuation=true). -【严格 JSON 输出格式】 -务必输出合法的纯 JSON 对象,格式如下: +[Strict JSON Output Format] +Always output a valid raw JSON object in the following format: { - "taskCompleted": boolean, // 当前任务是否已结束(如果 currentMmd 为 none,这里必须填 true) - "isLongTask": boolean, // 最新诉求是否是需要多步操作的复杂工程(普通技术问答、闲聊填 false) - "isContinuation": boolean, // 是否在延续 availableMmds 中的历史任务 - "continuationMmdFile": "string|null", // 若延续旧任务,精确填入 availableMmds 中的文件名(不含路径前缀),否则为 null - "newTaskLabel": "string|null" // 若是全新长任务,生成简短标签(≤30字符,kebab-case,如 "refactor-api"),否则为 null + "taskCompleted": boolean, // whether the current task has ended (if currentMmd is none, this MUST be true) + "isLongTask": boolean, // whether the latest request is a complex, multi-step engineering effort (ordinary technical Q&A or chit-chat → false) + "isContinuation": boolean, // whether this continues a historical task from availableMmds + "continuationMmdFile": "string|null", // if continuing an old task, fill in the exact filename from availableMmds (without path prefix); otherwise null + "newTaskLabel": "string|null" // if this is a brand-new long task, generate a short label (≤30 chars, kebab-case, e.g. "refactor-api"); otherwise null } -只输出纯 JSON 对象,绝不允许包含解释文字。`; +Output only the raw JSON object. Never include any explanatory text.`; // ─── Types ─────────────────────────────────────────────────────────────────── @@ -58,9 +58,9 @@ export function buildL15UserPrompt( ): string { const parts: string[] = []; - parts.push("## 1. 最近的对话上下文 (Recent 6 messages):"); + parts.push("## 1. Recent conversation context (Recent 6 messages):"); parts.push(recentMessages); - parts.push("\n## 2. 当前挂载的任务图 (Active Mermaid — 完整内容):"); + parts.push("\n## 2. Currently mounted task diagram (Active Mermaid — full content):"); if (currentMmd && currentMmd.filename) { parts.push(`**File:** ${currentMmd.filename}`); @@ -69,13 +69,13 @@ export function buildL15UserPrompt( } parts.push(`\n\`\`\`mermaid\n${currentMmd.content}\n\`\`\``); } else { - parts.push("(none - 当前处于闲置状态,无活跃任务)"); + parts.push("(none - currently idle, no active task)"); } - parts.push("\n## 3. 历史可用的任务图 (Available Mermaid task files):"); + parts.push("\n## 3. Historical task diagrams (Available Mermaid task files):"); if (metas.length === 0) { - parts.push("(none - 暂无历史长任务)"); + parts.push("(none - no historical long tasks yet)"); } else { for (const m of metas) { parts.push(`- **${m.filename}**`); @@ -96,6 +96,6 @@ export function buildL15UserPrompt( } } - parts.push("请严格根据系统指令的【三步思考链路】进行研判,并输出合法的 JSON 对象。"); + parts.push("Assess strictly according to the [three-step reasoning chain] in the system instructions, and output a valid JSON object."); return parts.join("\n"); } diff --git a/MemoryCore/src/offload/local-llm/prompts/l2-prompt.ts b/MemoryCore/src/offload/local-llm/prompts/l2-prompt.ts index b0fd3a5..d025c15 100644 --- a/MemoryCore/src/offload/local-llm/prompts/l2-prompt.ts +++ b/MemoryCore/src/offload/local-llm/prompts/l2-prompt.ts @@ -6,43 +6,43 @@ // ─── System Prompt ─────────────────────────────────────────────────────────── -export const L2_SYSTEM_PROMPT = `你是一个究极实用主义的 AI 任务拓扑架构师与视觉叙事者。 -你的核心逻辑是用尽量少的字符表达尽量多的信息,让LLM模型能看懂,不是为人类服务,尽量减少无用的视觉符号。任务是将底层工具调用记录,升维映射为一张高度语义化、表现力丰富且极度克制的 Mermaid (flowchart TD) 认知状态机。你要根据当前任务和意图,归纳"过去",要思考"未来"如何用这些已有的信息(你只需要记录已有信息,不需要写下一步规划)并标记"雷区"。保持图表的高度概括性。 +export const L2_SYSTEM_PROMPT = `You are an ultra-pragmatic AI task topology architect and visual storyteller. +Your core logic is to express as much information as possible in as few characters as possible, so that an LLM can understand it — this is not for human consumption — minimizing useless visual symbols. Your task is to lift low-level tool call records up into a highly semantic, richly expressive yet extremely restrained Mermaid (flowchart TD) cognitive state machine. Based on the current task and intent, you must summarize the "past", think about how the "future" can use this existing information (you only need to record existing information — do NOT write next-step plans), and flag the "minefields". Keep the diagram highly summarized. -【高阶认知与拓扑指南(你的自主权与极简原则)】 -1. 弹性聚合:你拥有决定节点拆合的完全自主权。对于连续的、意图相同的常规动作(如连续查看多个文件以了解上下文),建议合并为一个宏观节点;,但保留关键转折点或重大发现为独立节点。图表必须保持宏观和克制,绝不事无巨细地记流水账。 -2. 认知墓碑 (防重蹈覆辙):遇到彻底走不通的死胡同或引发严重报错的废弃方案,可以建立警示节点(status: blocked)(如果是价值不高的fail信息则不需要记录)。 -3. 结论导向的摘要:节点的 summary(注意:尽量小于150字)应聚焦于"得出了什么结论"或"发生了什么实质改变",而非罗列琐碎的数据或参数,记得保持极简原则。 -4. 要实事求是,你的任务是记录并归纳已经发生的事情,不是规划未来的具体操作,未发生的节点不要写,记录的已发生节点要有对应的消息来源(对应标注node_id)。 -【符号即语义:高维认知字典(你的核心武器)】为了极致压缩 Token 并为你下一步推理提供"认知锚点",请自由使用不同的mmd形状来代表不同的节点逻辑。让形状替你说话,省略冗余的文字描述。 +[High-Level Cognition & Topology Guide (your autonomy and minimalism principles)] +1. Elastic aggregation: you have full autonomy over splitting and merging nodes. For consecutive routine actions with the same intent (e.g., viewing several files in a row to gather context), merge them into one macro node; but keep key turning points or major discoveries as standalone nodes. The diagram must stay macro-level and restrained — never a blow-by-blow activity log. +2. Cognitive tombstones (avoid repeating mistakes): for utterly dead-end paths or abandoned approaches that triggered serious errors, you may create warning nodes (status: blocked) (low-value failure info need not be recorded). +3. Conclusion-oriented summaries: a node's summary (note: keep it under ~150 characters) should focus on "what conclusion was reached" or "what substantive change happened", rather than listing trivial data or parameters — remember the minimalism principle. +4. Be factual: your job is to record and summarize what has already happened, not to plan future concrete operations. Do not write nodes for things that have not happened, and every recorded node must have a corresponding message source (annotated via node_id). +[Symbols as Semantics: a high-dimensional cognitive dictionary (your core weapon)] To compress tokens to the extreme and give your next reasoning step "cognitive anchors", freely use different mmd shapes to represent different node logic. Let the shapes speak for you and omit redundant text descriptions. -【高度自由的拓扑与极简法则】 -1. 语义浓缩:既然形状已经表达了"领域",你的 summary 必须极其精简(≤150字),如"发现死锁"、"依赖冲突"、"已修复"。 -2. 弹性拓扑:自主使用带标签的连线(-->|测试失败|)和虚线(-.->|参考|)来构建"依赖树"和"假设验证环"。不要记流水账。 -3. 动态更新 (Token 极简): - - replace (增量微调):仅修改现有节点的状态、时间戳、短文本或追加极少节点时。 - - write (全量重写):逻辑大洗牌、重构图表或初始化时。 -注意:Existing Mermaid content 中每行开头都带有行号标记(如 "L1: ..."),这些行号仅供你在 replace 模式中引用,不是 MMD 内容的一部分。 +[Highly Free Topology & Minimalism Rules] +1. Semantic condensation: since the shapes already express the "domain", your summary must be extremely terse (≤150 chars), e.g. "deadlock found", "dependency conflict", "fixed". +2. Elastic topology: freely use labeled edges (-->|test failed|) and dashed edges (-.->|reference|) to build "dependency trees" and "hypothesis-verification loops". Do not keep an activity log. +3. Dynamic updates (token minimalism): + - replace (incremental tweak): only when modifying existing nodes' status, timestamps, short text, or appending very few nodes. + - write (full rewrite): for major logic reshuffles, diagram restructuring, or initialization. +Note: every line of the Existing Mermaid content starts with a line-number marker (e.g. "L1: ..."); these line numbers are only for your reference in replace mode and are NOT part of the MMD content. -【严格的工程底线】 -1.节点标准格式:NodeID["阶段名: 宏观动作简述
status: done|doing|paused|blocked
summary: 核心结论摘要
Timestamp: ISO8601"] -2. 全员归宿映射:输入的每一个新 tool_call_id,都必须在 node_mapping 中被分配到一个 Node ID;MMD里的每一个node都应该有源头的tool_call消息来源,不能乱编,绝对不允许遗漏!(Node_id和tool_call_id是一对多的关系) -3. 你可以通过各种整合方法,尽量把更新后mmd文件大小控制在4000字以内 +[Strict Engineering Baselines] +1. Standard node format: NodeID["stage name: brief macro action
status: done|doing|paused|blocked
summary: core conclusion summary
Timestamp: ISO8601"] +2. Full-coverage mapping: every single new tool_call_id in the input MUST be assigned to a Node ID in node_mapping; every node in the MMD must have an originating tool_call message source — never fabricate, and absolutely never omit any! (Node_id to tool_call_id is a one-to-many relationship.) +3. Use whatever consolidation methods you can to keep the updated mmd file under 4000 characters. -【严格时间戳与元数据规则】 -1. 顶部元数据(必填):%%{ "taskGoal": "一句话总结此次任务的目标(可动态更新)", "progress(0-100)": "进度百分比(严格点,几乎确认完成再打到90+)", createdTime": "ISO时间", "updatedTime": "ISO时间" }%%(updatedTime为node中的最新时间)。 -2. 节点内时间:如果合并了多个新条目,节点内的 Timestamp 必须取其中最新的 ISO 时间。 +[Strict Timestamp & Metadata Rules] +1. Top metadata (required): %%{ "taskGoal": "one-sentence summary of this task's goal (may be updated dynamically)", "progress(0-100)": "progress percentage (be strict — only go 90+ when completion is almost certain)", createdTime": "ISO time", "updatedTime": "ISO time" }%% (updatedTime is the latest time among the nodes). +2. In-node time: if multiple new entries are merged into a node, the node's Timestamp must be the latest ISO time among them. -【严格 JSON 输出格式】 -务必正确转义双引号。所有 Mermaid 代码(无论是 mmd_content 还是 replace_blocks 中的 content)都必须用 \`\`\`mermaid ... \`\`\` 代码块包裹起来。必须输出如下 JSON 结构: +[Strict JSON Output Format] +Escape double quotes correctly. All Mermaid code (whether mmd_content or the content in replace_blocks) must be wrapped in a \`\`\`mermaid ... \`\`\` code block. You must output the following JSON structure: { - "file_action": "replace 或 write", - "mmd_content": "完整的、带转义的 .mmd 代码,必须用 \`\`\`mermaid ... \`\`\` 包裹。(仅在 file_action 为 write 时填写,否则必须设为 null)", + "file_action": "replace or write", + "mmd_content": "the complete, escaped .mmd code, wrapped in \`\`\`mermaid ... \`\`\`. (Fill this in only when file_action is write; otherwise it MUST be null)", "replace_blocks": [ { - "start_line": "需要更新范围的起始行号(整数,对应 Existing Mermaid content 中的 L 标号)", - "end_line": "需要更新范围的结束行号(整数,包含该行)。要在某行之前插入新内容而不删除任何行,将 start_line 设为该行号,end_line 设为 start_line - 1", - "content": "替换后的新内容(不需要带行号前缀),必须用 \`\`\`mermaid ... \`\`\` 包裹" + "start_line": "starting line number of the range to update (integer, matching the L labels in Existing Mermaid content)", + "end_line": "ending line number of the range to update (integer, inclusive). To insert new content before a line without deleting any lines, set start_line to that line number and end_line to start_line - 1", + "content": "the new replacement content (no line-number prefixes), wrapped in \`\`\`mermaid ... \`\`\`" } ], "node_mapping": { @@ -51,7 +51,7 @@ export const L2_SYSTEM_PROMPT = `你是一个究极实用主义的 AI 任务拓 } } -仅输出纯 JSON 对象,绝不允许包含任何解释。`; +Output only the raw JSON object. Never include any explanation.`; // ─── Types ─────────────────────────────────────────────────────────────────── @@ -82,26 +82,26 @@ export function buildL2UserPrompt(opts: { // History section if (recentHistory) { - parts.push(`## 近期对话历史:\n${recentHistory}`); + parts.push(`## Recent conversation history:\n${recentHistory}`); } else { - parts.push("## 近期对话历史:\n(无可用历史)"); + parts.push("## Recent conversation history:\n(no history available)"); } if (currentTurn) { - parts.push(`\n## 当前最新一轮:\n${currentTurn}`); + parts.push(`\n## Latest turn:\n${currentTurn}`); } parts.push(`\n## MMD prefix: ${mmdPrefix}`); - parts.push(`(所有节点 ID 必须以此前缀开头,如 ${mmdPrefix}-N1, ${mmdPrefix}-N2...)`); + parts.push(`(All node IDs must start with this prefix, e.g. ${mmdPrefix}-N1, ${mmdPrefix}-N2...)`); parts.push(`\n## Current task label: ${taskLabel}`); // Char count warning if (charCount > 2500) { parts.push(`\n## Current MMD size: ${charCount} chars (budget: 4000 chars)`); - parts.push("⚠ 接近上限,请积极合并节点、精简 summary,优先使用 replace 模式微调而非 write 全量重写。"); + parts.push("⚠ Approaching the limit — aggressively merge nodes and trim summaries; prefer replace-mode tweaks over full write rewrites."); } else if (charCount > 2000) { parts.push(`\n## Current MMD size: ${charCount} chars (budget: 4000 chars)`); - parts.push("注意控制增长,合并同类节点。"); + parts.push("Watch the growth; merge similar nodes."); } // Existing MMD with line numbers @@ -122,6 +122,6 @@ export function buildL2UserPrompt(opts: { parts.push(`${i + 1}. [${e.toolCallId}] ${e.toolCall} → ${e.summary} (${e.timestamp})`); } - parts.push("\n请根据系统指令生成/更新 Mermaid 流程图,并输出合法的 JSON 对象(含 node_mapping)。"); + parts.push("\nGenerate/update the Mermaid flowchart according to the system instructions, and output a valid JSON object (including node_mapping)."); return parts.join("\n"); } diff --git a/MemoryCore/src/offload_server/prompts/l1-prompt.ts b/MemoryCore/src/offload_server/prompts/l1-prompt.ts index fe76247..bd87866 100644 --- a/MemoryCore/src/offload_server/prompts/l1-prompt.ts +++ b/MemoryCore/src/offload_server/prompts/l1-prompt.ts @@ -7,27 +7,27 @@ const PARAMS_MAX_LEN = 500; const RESULT_MAX_LEN = 2000; const COMPRESS_THRESHOLD = 200; -export const L1_SYSTEM_PROMPT = `你是一个专为 AI 编码助手提供支持的"工具结果摘要器"。你的核心任务是深度理解当前的对话上下文,并将繁杂的工具调用与执行结果(一对toolcall和tool result整合成一条summary输出),提炼为高信息密度的 JSON 数组。 +export const L1_SYSTEM_PROMPT = `You are a "tool result summarizer" built to support an AI coding assistant. Your core task is to deeply understand the current conversation context and distill verbose tool calls and their execution results (each toolcall/tool result pair is consolidated into one summary output) into a high-information-density JSON array. -在生成摘要前,请务必进行以下内部思考: -1. 任务对齐:结合最近的对话记录,识别用户当前的核心目标和最新意图。若上下文存在冲突,始终以最新的用户意图为准。 -2. 价值过滤:忽略工具如何工作的冗余细节,直接提取"发现了什么关键线索"、"做了什么关键动作"、"修改了什么具体内容"或"遇到了什么具体报错"。 -3. 影响评估:判断该结果对当前任务的实质性影响(例如:证实了某个假设、推进了哪一步、做出了什么决策,或因为什么报错导致了阻塞)。 +Before producing summaries, always perform the following internal reasoning: +1. Task alignment: use the recent conversation records to identify the user's current core goal and latest intent. If the context conflicts, always defer to the most recent user intent. +2. Value filtering: ignore redundant details about how the tool works; directly extract "what key clue was discovered", "what key action was taken", "what specific content was modified", or "what specific error was encountered". +3. Impact assessment: judge the result's substantive impact on the current task (e.g., it confirmed a hypothesis, advanced a particular step, led to a decision, or caused a blockage due to a specific error). -【输出格式要求】 -你必须且只能输出一个合法的 JSON 对象数组 [{...}],每个对象**必须**包含以下字段: -- "tool_call": 工具调用的简洁描述。处理规则如下: - · 如果输入中该 tool pair 标记了 [NEEDS_COMPRESS],你必须将工具名+关键参数压缩为一句简洁的描述(≤150字符),保留工具名、操作目标(如文件路径、命令意图),省略内联脚本/大段内容的细节。 - 示例:exec({"command":"python3 -c 'import csv; ...200行脚本...'"}) → "exec: 运行 Python (xx/xx/xx.sh,标明具体路径和文件)脚本分析 sales_channels.csv 数据质量" - 示例:write_file({"path":"/root/app.py","content":"...5000字符..."}) → "write_file: 写入 /root/app.py (Flask 应用主文件),大致内容是……" - · 如果未标记 [NEEDS_COMPRESS],直接简述工具与参数即可(系统会用原始值覆盖)。 -- "summary": 融合上述思考的精炼总结(≤200个字符)。必须一针见血地说清楚结果的业务价值,以及它对任务的推进/阻塞作用。 -- "tool_call_id": 原始的 tool_call_id(必须原样透传)。 -- "timestamp": 原始的 ISO 8601 时间戳(必须原样透传)。 -- "score"(**必填**): 结合信息密度和任务目的分析summary对于原文的可替代性,范围在0-10之间,越接近10表示summary越能替代原文。 +[Output Format Requirements] +You must output one and only one valid JSON array of objects [{...}]; each object MUST contain the following fields: +- "tool_call": a concise description of the tool call. Handling rules: + · If the tool pair in the input is marked [NEEDS_COMPRESS], you must compress the tool name + key parameters into a single concise description (≤150 characters), keeping the tool name and the operation target (e.g., file path, command intent), while omitting the details of inline scripts / large content blocks. + Example: exec({"command":"python3 -c 'import csv; ...200-line script...'"}) → "exec: run a Python script (xx/xx/xx.sh — state the specific path and file) to analyze data quality of sales_channels.csv" + Example: write_file({"path":"/root/app.py","content":"...5000 chars..."}) → "write_file: write /root/app.py (Flask app main file); its rough contents are ..." + · If not marked [NEEDS_COMPRESS], just briefly describe the tool and its parameters (the system will overwrite this with the original value). +- "summary": a distilled summary that fuses the reasoning above (≤200 characters). It must state, incisively, the business value of the result and how it advances or blocks the task. +- "tool_call_id": the original tool_call_id (must be passed through verbatim). +- "timestamp": the original ISO 8601 timestamp (must be passed through verbatim). +- "score" (REQUIRED): considering information density and the task's purpose, rate how well the summary can substitute for the original text, on a scale of 0-10 — the closer to 10, the better the summary can replace the original. -【严格规则】 -只允许输出纯 JSON 数组,严禁输出思考过程或其他解释性文本。`; +[Strict Rules] +Output only the raw JSON array. Never output your reasoning process or any other explanatory text.`; /** * Build the L1 user prompt for summarization. @@ -38,8 +38,8 @@ export function buildL1UserPrompt( ): string { const parts: string[] = []; - parts.push("## 最近的对话上下文(用于理解当前任务):"); - parts.push(recentContext || "(无可用上下文)"); + parts.push("## Recent conversation context (for understanding the current task):"); + parts.push(recentContext || "(no context available)"); parts.push("\n## Tool call/result pairs to summarize:"); for (let i = 0; i < pairs.length; i++) { diff --git a/MemoryCore/src/offload_server/prompts/l15-prompt.ts b/MemoryCore/src/offload_server/prompts/l15-prompt.ts index 8ceaa7f..6e2a27e 100644 --- a/MemoryCore/src/offload_server/prompts/l15-prompt.ts +++ b/MemoryCore/src/offload_server/prompts/l15-prompt.ts @@ -3,25 +3,25 @@ */ import type { MmdMeta } from "../types.js"; -export const L15_SYSTEM_PROMPT = `你是一个面向 AI 编码助手的"任务生命周期门神"。 -你的职责是交叉分析提供的三个输入源,精准研判任务状态,并输出纯 JSON 对象。 +export const L15_SYSTEM_PROMPT = `You are a "task lifecycle gatekeeper" for an AI coding assistant. +Your duty is to cross-analyze the three provided input sources, precisely assess the task state, and output a raw JSON object. -【输入数据利用指南(必须遵循的思考链路)】 -1. 第一步 - 剖析 recentMessages(识别意图):根据当前和历史对话,提取用户最新回复的核心诉求。判断是"继续排查"、"宣布完工(如:跑通了)"、"单轮闲聊问答"还是"开启全新需求"。 -2. 第二步 - 对齐 currentMmd(评估当前基线):将用户的最新意图与 currentMmd 的完整 Mermaid 内容进行比对——关注 taskGoal、各节点的 status(done/doing/todo)以及 summary。如果诉求完全超出了当前图表的范畴或目标已实现(所有节点 done 且无后续),则 taskCompleted 为 true。若仍在解决图表中的子问题(包括 doing 节点或修 bug),则为 false。(如果没有currentMmd,就只根据当前对话和历史对话来判断是否继续任务) -3. 第三步 - 检索 availableMmds(判断是否延续):如果判定要开启新任务(isLongTask=true 且 taskCompleted=true/当前无任务),必须扫描 availableMmds 的 taskGoal 和时间信息。若新诉求与列表中某个旧任务高度重合(如回到昨天没做完的模块),则是延续(isContinuation=true)。 +[Input Usage Guide (three-step reasoning chain you MUST follow)] +1. Step 1 - Dissect recentMessages (identify intent): based on the current and past conversation, extract the core request of the user's latest reply. Decide whether it is "continuing the investigation", "declaring the work done (e.g., 'it runs now')", "a single-turn casual Q&A", or "starting a brand-new requirement". +2. Step 2 - Align with currentMmd (assess the current baseline): compare the user's latest intent against the full Mermaid content of currentMmd — focus on taskGoal, each node's status (done/doing/todo), and the summaries. If the request falls entirely outside the diagram's scope, or the goal has already been achieved (all nodes done with no follow-up), then taskCompleted is true. If the user is still working on a sub-problem within the diagram (including doing nodes or bug fixing), it is false. (If there is no currentMmd, judge whether the task continues solely from the current and past conversation.) +3. Step 3 - Search availableMmds (decide continuation): if you determine a new task is starting (isLongTask=true and taskCompleted=true / there is no current task), you MUST scan the taskGoal and time information of availableMmds. If the new request heavily overlaps with an old task in the list (e.g., returning to yesterday's unfinished module), it is a continuation (isContinuation=true). -【严格 JSON 输出格式】 -务必输出合法的纯 JSON 对象,格式如下: +[Strict JSON Output Format] +Always output a valid raw JSON object in the following format: { - "taskCompleted": boolean, // 当前任务是否已结束(如果 currentMmd 为 none,这里必须填 true) - "isLongTask": boolean, // 最新诉求是否是需要多步操作的复杂工程(普通技术问答、闲聊填 false) - "isContinuation": boolean, // 是否在延续 availableMmds 中的历史任务 - "continuationMmdFile": "string|null", // 若延续旧任务,精确填入 availableMmds 中的文件名(不含路径前缀),否则为 null - "newTaskLabel": "string|null" // 若是全新长任务,生成简短标签(≤30字符,kebab-case,如 "refactor-api"),否则为 null + "taskCompleted": boolean, // whether the current task has ended (if currentMmd is none, this MUST be true) + "isLongTask": boolean, // whether the latest request is a complex, multi-step engineering effort (ordinary technical Q&A or chit-chat → false) + "isContinuation": boolean, // whether this continues a historical task from availableMmds + "continuationMmdFile": "string|null", // if continuing an old task, fill in the exact filename from availableMmds (without path prefix); otherwise null + "newTaskLabel": "string|null" // if this is a brand-new long task, generate a short label (≤30 chars, kebab-case, e.g. "refactor-api"); otherwise null } -只输出纯 JSON 对象,绝不允许包含解释文字。`; +Output only the raw JSON object. Never include any explanatory text.`; export interface L15CurrentMmd { filename: string; @@ -38,21 +38,21 @@ export function buildL15UserPrompt( ): string { const parts: string[] = []; - parts.push("## 1. 最近的对话上下文 (Recent messages):"); + parts.push("## 1. Recent conversation context (Recent messages):"); parts.push(recentMessages); - parts.push("\n## 2. 当前挂载的任务图 (Active Mermaid — 完整内容):"); + parts.push("\n## 2. Currently mounted task diagram (Active Mermaid — full content):"); if (currentMmd && currentMmd.filename) { parts.push(`**File:** ${currentMmd.filename}`); parts.push(`\n\`\`\`mermaid\n${currentMmd.content}\n\`\`\``); } else { - parts.push("(none - 当前处于闲置状态,无活跃任务)"); + parts.push("(none - currently idle, no active task)"); } - parts.push("\n## 3. 历史可用的任务图 (Available Mermaid task files):"); + parts.push("\n## 3. Historical task diagrams (Available Mermaid task files):"); if (metas.length === 0) { - parts.push("(none - 暂无历史长任务)"); + parts.push("(none - no historical long tasks yet)"); } else { for (const m of metas) { const total = m.doneCount + m.doingCount + m.todoCount; @@ -75,7 +75,7 @@ export function buildL15UserPrompt( } parts.push( - "请严格根据系统指令的【三步思考链路】进行研判,并输出合法的 JSON 对象。", + "Assess strictly according to the [three-step reasoning chain] in the system instructions, and output a valid JSON object.", ); return parts.join("\n"); } diff --git a/MemoryCore/src/offload_server/prompts/l2-prompt.ts b/MemoryCore/src/offload_server/prompts/l2-prompt.ts index 0999c1a..803472b 100644 --- a/MemoryCore/src/offload_server/prompts/l2-prompt.ts +++ b/MemoryCore/src/offload_server/prompts/l2-prompt.ts @@ -3,43 +3,43 @@ */ import type { OffloadEntry } from "../types.js"; -export const L2_SYSTEM_PROMPT = `你是一个究极实用主义的 AI 任务拓扑架构师与视觉叙事者。 -你的核心逻辑是用尽量少的字符表达尽量多的信息,让LLM模型能看懂,不是为人类服务,尽量减少无用的视觉符号。任务是将底层工具调用记录,升维映射为一张高度语义化、表现力丰富且极度克制的 Mermaid (flowchart TD) 认知状态机。你要根据当前任务和意图,归纳"过去",要思考"未来"如何用这些已有的信息(你只需要记录已有信息,不需要写下一步规划)并标记"雷区"。保持图表的高度概括性。 +export const L2_SYSTEM_PROMPT = `You are an ultra-pragmatic AI task topology architect and visual storyteller. +Your core logic is to express as much information as possible in as few characters as possible, so that an LLM can understand it — this is not for human consumption — minimizing useless visual symbols. Your task is to lift low-level tool call records up into a highly semantic, richly expressive yet extremely restrained Mermaid (flowchart TD) cognitive state machine. Based on the current task and intent, you must summarize the "past", think about how the "future" can use this existing information (you only need to record existing information — do NOT write next-step plans), and flag the "minefields". Keep the diagram highly summarized. -【高阶认知与拓扑指南(你的自主权与极简原则)】 -1. 弹性聚合:你拥有决定节点拆合的完全自主权。对于连续的、意图相同的常规动作(如连续查看多个文件以了解上下文),建议合并为一个宏观节点;,但保留关键转折点或重大发现为独立节点。图表必须保持宏观和克制,绝不事无巨细地记流水账。 -2. 认知墓碑 (防重蹈覆辙):遇到彻底走不通的死胡同或引发严重报错的废弃方案,可以建立警示节点(status: blocked)(如果是价值不高的fail信息则不需要记录)。 -3. 结论导向的摘要:节点的 summary(注意:尽量小于150字)应聚焦于"得出了什么结论"或"发生了什么实质改变",而非罗列琐碎的数据或参数,记得保持极简原则。 -4. 要实事求是,你的任务是记录并归纳已经发生的事情,不是规划未来的具体操作,未发生的节点不要写,记录的已发生节点要有对应的消息来源(对应标注node_id)。 -【符号即语义:高维认知字典(你的核心武器)】为了极致压缩 Token 并为你下一步推理提供"认知锚点",请自由使用不同的mmd形状来代表不同的节点逻辑。让形状替你说话,省略冗余的文字描述。 +[High-Level Cognition & Topology Guide (your autonomy and minimalism principles)] +1. Elastic aggregation: you have full autonomy over splitting and merging nodes. For consecutive routine actions with the same intent (e.g., viewing several files in a row to gather context), merge them into one macro node; but keep key turning points or major discoveries as standalone nodes. The diagram must stay macro-level and restrained — never a blow-by-blow activity log. +2. Cognitive tombstones (avoid repeating mistakes): for utterly dead-end paths or abandoned approaches that triggered serious errors, you may create warning nodes (status: blocked) (low-value failure info need not be recorded). +3. Conclusion-oriented summaries: a node's summary (note: keep it under ~150 characters) should focus on "what conclusion was reached" or "what substantive change happened", rather than listing trivial data or parameters — remember the minimalism principle. +4. Be factual: your job is to record and summarize what has already happened, not to plan future concrete operations. Do not write nodes for things that have not happened, and every recorded node must have a corresponding message source (annotated via node_id). +[Symbols as Semantics: a high-dimensional cognitive dictionary (your core weapon)] To compress tokens to the extreme and give your next reasoning step "cognitive anchors", freely use different mmd shapes to represent different node logic. Let the shapes speak for you and omit redundant text descriptions. -【高度自由的拓扑与极简法则】 -1. 语义浓缩:既然形状已经表达了"领域",你的 summary 必须极其精简(≤150字),如"发现死锁"、"依赖冲突"、"已修复"。 -2. 弹性拓扑:自主使用带标签的连线(-->|测试失败|)和虚线(-.->|参考|)来构建"依赖树"和"假设验证环"。不要记流水账。 -3. 动态更新 (Token 极简): - - replace (增量微调):仅修改现有节点的状态、时间戳、短文本或追加极少节点时。 - - write (全量重写):逻辑大洗牌、重构图表或初始化时。 -注意:Existing Mermaid content 中每行开头都带有行号标记(如 "L1: ..."),这些行号仅供你在 replace 模式中引用,不是 MMD 内容的一部分。 +[Highly Free Topology & Minimalism Rules] +1. Semantic condensation: since the shapes already express the "domain", your summary must be extremely terse (≤150 chars), e.g. "deadlock found", "dependency conflict", "fixed". +2. Elastic topology: freely use labeled edges (-->|test failed|) and dashed edges (-.->|reference|) to build "dependency trees" and "hypothesis-verification loops". Do not keep an activity log. +3. Dynamic updates (token minimalism): + - replace (incremental tweak): only when modifying existing nodes' status, timestamps, short text, or appending very few nodes. + - write (full rewrite): for major logic reshuffles, diagram restructuring, or initialization. +Note: every line of the Existing Mermaid content starts with a line-number marker (e.g. "L1: ..."); these line numbers are only for your reference in replace mode and are NOT part of the MMD content. -【严格的工程底线】 -1.节点标准格式:NodeID["阶段名: 宏观动作简述
status: done|doing|paused|blocked
summary: 核心结论摘要
Timestamp: ISO8601"] -2. 全员归宿映射:输入的每一个新 tool_call_id,都必须在 node_mapping 中被分配到一个 Node ID;MMD里的每一个node都应该有源头的tool_call消息来源,不能乱编,绝对不允许遗漏!(Node_id和tool_call_id是一对多的关系) -3. 你可以通过各种整合方法,尽量把更新后mmd文件大小控制在4000字以内 +[Strict Engineering Baselines] +1. Standard node format: NodeID["stage name: brief macro action
status: done|doing|paused|blocked
summary: core conclusion summary
Timestamp: ISO8601"] +2. Full-coverage mapping: every single new tool_call_id in the input MUST be assigned to a Node ID in node_mapping; every node in the MMD must have an originating tool_call message source — never fabricate, and absolutely never omit any! (Node_id to tool_call_id is a one-to-many relationship.) +3. Use whatever consolidation methods you can to keep the updated mmd file under 4000 characters. -【严格时间戳与元数据规则】 -1. 顶部元数据(必填):%%{ "taskGoal": "一句话总结此次任务的目标(可动态更新)", "progress(0-100)": "进度百分比(严格点,几乎确认完成再打到90+)", createdTime": "ISO时间", "updatedTime": "ISO时间" }%%(updatedTime为node中的最新时间)。 -2. 节点内时间:如果合并了多个新条目,节点内的 Timestamp 必须取其中最新的 ISO 时间。 +[Strict Timestamp & Metadata Rules] +1. Top metadata (required): %%{ "taskGoal": "one-sentence summary of this task's goal (may be updated dynamically)", "progress(0-100)": "progress percentage (be strict — only go 90+ when completion is almost certain)", createdTime": "ISO time", "updatedTime": "ISO time" }%% (updatedTime is the latest time among the nodes). +2. In-node time: if multiple new entries are merged into a node, the node's Timestamp must be the latest ISO time among them. -【严格 JSON 输出格式】 -务必正确转义双引号。所有 Mermaid 代码(无论是 mmd_content 还是 replace_blocks 中的 content)都必须用 \`\`\`mermaid ... \`\`\` 代码块包裹起来。必须输出如下 JSON 结构: +[Strict JSON Output Format] +Escape double quotes correctly. All Mermaid code (whether mmd_content or the content in replace_blocks) must be wrapped in a \`\`\`mermaid ... \`\`\` code block. You must output the following JSON structure: { - "file_action": "replace 或 write", - "mmd_content": "完整的、带转义的 .mmd 代码,必须用 \`\`\`mermaid ... \`\`\` 包裹。(仅在 file_action 为 write 时填写,否则必须设为 null)", + "file_action": "replace or write", + "mmd_content": "the complete, escaped .mmd code, wrapped in \`\`\`mermaid ... \`\`\`. (Fill this in only when file_action is write; otherwise it MUST be null)", "replace_blocks": [ { - "start_line": "需要更新范围的起始行号(整数,对应 Existing Mermaid content 中的 L 标号)", - "end_line": "需要更新范围的结束行号(整数,包含该行)。要在某行之前插入新内容而不删除任何行,将 start_line 设为该行号,end_line 设为 start_line - 1", - "content": "替换后的新内容(不需要带行号前缀),必须用 \`\`\`mermaid ... \`\`\` 包裹" + "start_line": "starting line number of the range to update (integer, matching the L labels in Existing Mermaid content)", + "end_line": "ending line number of the range to update (integer, inclusive). To insert new content before a line without deleting any lines, set start_line to that line number and end_line to start_line - 1", + "content": "the new replacement content (no line-number prefixes), wrapped in \`\`\`mermaid ... \`\`\`" } ], "node_mapping": { @@ -48,8 +48,8 @@ export const L2_SYSTEM_PROMPT = `你是一个究极实用主义的 AI 任务拓 } } -注意:node_mapping 中的 Node ID 必须是 MMD 中实际使用的完整 ID(包含 MMD prefix,如 "001-N1"),不能只写短 ID(如 "N1")。 -仅输出纯 JSON 对象,绝不允许包含任何解释。`; +Note: Node IDs in node_mapping must be the full IDs actually used in the MMD (including the MMD prefix, e.g. "001-N1"), never just the short ID (e.g. "N1"). +Output only the raw JSON object. Never include any explanation.`; /** * Build the L2 user prompt for MMD generation. @@ -68,26 +68,26 @@ export function buildL2UserPrompt(opts: { // History section if (recentHistory) { - parts.push(`## 近期对话历史:\n${recentHistory}`); + parts.push(`## Recent conversation history:\n${recentHistory}`); } else { - parts.push("## 近期对话历史:\n(无可用历史)"); + parts.push("## Recent conversation history:\n(no history available)"); } if (currentTurn) { - parts.push(`\n## 当前最新一轮:\n${currentTurn}`); + parts.push(`\n## Latest turn:\n${currentTurn}`); } parts.push(`\n## MMD prefix: ${mmdPrefix}`); - parts.push(`(所有节点 ID 必须以此前缀开头,如 ${mmdPrefix}-N1, ${mmdPrefix}-N2...)`); + parts.push(`(All node IDs must start with this prefix, e.g. ${mmdPrefix}-N1, ${mmdPrefix}-N2...)`); parts.push(`\n## Current task label: ${taskLabel}`); // Char count warning if (charCount > 2500) { parts.push(`\n## Current MMD size: ${charCount} chars (budget: 4000 chars)`); - parts.push("⚠ 接近上限,请积极合并节点、精简 summary,优先使用 replace 模式微调而非 write 全量重写。"); + parts.push("⚠ Approaching the limit — aggressively merge nodes and trim summaries; prefer replace-mode tweaks over full write rewrites."); } else if (charCount > 2000) { parts.push(`\n## Current MMD size: ${charCount} chars (budget: 4000 chars)`); - parts.push("注意控制增长,合并同类节点。"); + parts.push("Watch the growth; merge similar nodes."); } // Existing MMD with line numbers @@ -108,6 +108,6 @@ export function buildL2UserPrompt(opts: { parts.push(`${i + 1}. [${e.tool_call_id}] ${e.tool_call} → ${e.summary} (${e.timestamp})`); } - parts.push("\n请根据系统指令生成/更新 Mermaid 流程图,并输出合法的 JSON 对象(含 node_mapping)。"); + parts.push("\nGenerate/update the Mermaid flowchart according to the system instructions, and output a valid JSON object (including node_mapping)."); return parts.join("\n"); } diff --git a/MemoryKnowledge/docs/api/openapi.yaml b/MemoryKnowledge/docs/api/openapi.yaml new file mode 100644 index 0000000..4335743 --- /dev/null +++ b/MemoryKnowledge/docs/api/openapi.yaml @@ -0,0 +1,9 @@ +# GODCALL stub — upstream's combined Dockerfile COPYs this path but the file is +# absent from the public repo at this commit. The knowledge server serves it as +# the Swagger document; regenerate/replace when upstream restores the real spec. +openapi: 3.0.3 +info: + title: GODCALL Knowledge API + description: Wiki + CodeGraph knowledge service (self-hosted GODCALL fork). + version: 0.1.0 +paths: {} diff --git a/MemoryKnowledge/src/engines/wiki/ingest-v2/merge.ts b/MemoryKnowledge/src/engines/wiki/ingest-v2/merge.ts index d9f14a8..1e3a5cf 100644 --- a/MemoryKnowledge/src/engines/wiki/ingest-v2/merge.ts +++ b/MemoryKnowledge/src/engines/wiki/ingest-v2/merge.ts @@ -81,7 +81,7 @@ export async function mergePage( // locked → 跳过,保护用户手工编辑。 const oldParsed = parseFrontmatter(existingContent); if (oldParsed.frontmatter.locked === true) { - return { action: "skip", reason: "目标页 locked,跳过合并" }; + return { action: "skip", reason: "target page is locked; skipping merge" }; } const candParsed = parseFrontmatter(candidateContent); diff --git a/MemoryKnowledge/src/engines/wiki/ingest-v2/overview.ts b/MemoryKnowledge/src/engines/wiki/ingest-v2/overview.ts index bf768bd..c932264 100644 --- a/MemoryKnowledge/src/engines/wiki/ingest-v2/overview.ts +++ b/MemoryKnowledge/src/engines/wiki/ingest-v2/overview.ts @@ -87,7 +87,7 @@ export async function generateOverview(projectPath: string, llm: LlmClient): Pro const wikiDir = join(projectPath, "wiki"); const briefs = collectBriefs(wikiDir); if (briefs.length < 2) { - log.debug("页面太少,跳过 overview 生成", { pages: briefs.length }); + log.debug("too few pages, skipping overview generation", { pages: briefs.length }); return false; } diff --git a/MemoryKnowledge/src/engines/wiki/ingest-v2/prompts.ts b/MemoryKnowledge/src/engines/wiki/ingest-v2/prompts.ts index 3c20bf1..dc2b1a4 100644 --- a/MemoryKnowledge/src/engines/wiki/ingest-v2/prompts.ts +++ b/MemoryKnowledge/src/engines/wiki/ingest-v2/prompts.ts @@ -32,7 +32,7 @@ export interface PageForUpdate { function formatExistingPages(existingPages: ExistingPageInfo[]): string { return existingPages.length > 0 ? existingPages - .map((p) => `- [${p.type}] ${p.relPath}${p.title ? ` — ${p.title}` : ""}${p.description ? `(${p.description})` : ""}`) + .map((p) => `- [${p.type}] ${p.relPath}${p.title ? ` — ${p.title}` : ""}${p.description ? ` (${p.description})` : ""}`) .join("\n") : "(wiki is empty — this is the first source)"; } @@ -164,7 +164,7 @@ export function buildGeneratePrompt(args: { const existingList = existingPages.length > 0 ? existingPages - .map((p) => `- [${p.type}] ${p.relPath}${p.title ? ` — ${p.title}` : ""}${p.description ? `(${p.description})` : ""}`) + .map((p) => `- [${p.type}] ${p.relPath}${p.title ? ` — ${p.title}` : ""}${p.description ? ` (${p.description})` : ""}`) .join("\n") : "(wiki is empty — this is the first source)"; diff --git a/MemoryKnowledge/src/source-fetcher/git-fetcher.ts b/MemoryKnowledge/src/source-fetcher/git-fetcher.ts index d6bd51e..a3106d6 100644 --- a/MemoryKnowledge/src/source-fetcher/git-fetcher.ts +++ b/MemoryKnowledge/src/source-fetcher/git-fetcher.ts @@ -55,10 +55,18 @@ export class GitSourceFetcher implements ISourceFetcher { } validate(sourceUrl: string): void { - // 第一版:仅支持 public HTTPS 仓库(SSH / 私有仓库鉴权见文档 005)。 - if (!sourceUrl.startsWith("https://")) { + // GODCALL: plain http:// is allowed when KNOWLEDGE_ALLOW_HTTP is set — + // required for self-hosted Gitea reachable only over the private tailnet. + const allowHttp = /^(1|true|yes|on)$/i.test( + (process.env.KNOWLEDGE_ALLOW_HTTP ?? "").trim(), + ); + const okProtocol = + sourceUrl.startsWith("https://") || (allowHttp && sourceUrl.startsWith("http://")); + if (!okProtocol) { throw new Error( - "first version only supports public HTTPS repos; SSH/private repo support coming soon", + allowHttp + ? "repo_url must be an http(s):// git URL" + : "only HTTPS repos are supported (set KNOWLEDGE_ALLOW_HTTP=1 to allow http://); SSH is not supported", ); } const host = this.extractHost(sourceUrl); diff --git a/MemoryPanel/web/index.html b/MemoryPanel/web/index.html index 629ca17..d30036b 100644 --- a/MemoryPanel/web/index.html +++ b/MemoryPanel/web/index.html @@ -1,9 +1,9 @@ - + - Memory Hub + GODCALL diff --git a/MemoryPanel/web/public/logo.png b/MemoryPanel/web/public/logo.png index 286c7cc..9605104 100644 Binary files a/MemoryPanel/web/public/logo.png and b/MemoryPanel/web/public/logo.png differ diff --git a/MemoryPanel/web/src/App.tsx b/MemoryPanel/web/src/App.tsx index 025dab2..f978d49 100644 --- a/MemoryPanel/web/src/App.tsx +++ b/MemoryPanel/web/src/App.tsx @@ -28,7 +28,7 @@ export default function App() { if (auth === null) { return (
-
正在检测登录态…
+
Checking login status…
); } diff --git a/MemoryPanel/web/src/components/LoginGate.tsx b/MemoryPanel/web/src/components/LoginGate.tsx index c861257..f327b4d 100644 --- a/MemoryPanel/web/src/components/LoginGate.tsx +++ b/MemoryPanel/web/src/components/LoginGate.tsx @@ -233,7 +233,7 @@ export default function LoginGate({ // 并把下拉 placeholder 切成"加载失败",用户可刷新重试。 if (cancelled) return; setInstancesError(true); - setError(`加载记忆实例列表失败,请刷新页面重试${err instanceof Error ? `(${err.message})` : ''}`); + setError(`Failed to load the memory instance list, please refresh the page and retry${err instanceof Error ? ` (${err.message})` : ''}`); }); return () => { cancelled = true; @@ -243,12 +243,12 @@ export default function LoginGate({ async function submit(e?: React.FormEvent) { e?.preventDefault(); if (!instanceId) { - setError('请选择记忆实例。'); + setError('Please select a memory instance.'); return; } const key = userKey.trim(); if (!key) { - setError('请输入你的 user_key(sk-mem-…)。'); + setError('Please enter your user_key (sk-mem-…).'); return; } setSubmitting(true); @@ -256,12 +256,12 @@ export default function LoginGate({ try { const { valid, user } = await authVerifyApi.verify(instanceId, key); if (!valid) { - setError('user_key 无效或已吊销,请确认后重新输入。'); + setError('user_key is invalid or has been revoked. Please check it and re-enter.'); setSubmitting(false); return; } if (!user) { - setError('登录响应缺少用户信息(data.user 为空),请联系后端确认 auth/verify 契约。'); + setError('The login response is missing user information (data.user is empty). Please contact backend to confirm the auth/verify contract.'); setSubmitting(false); return; } @@ -285,17 +285,17 @@ export default function LoginGate({ {/* ====== 左侧深色面板 ====== */}
- Memory Hub - Memory Hub + GODCALL + GODCALL

- TencentDB Memory Hub + GODCALL

- 集中管理 Agent 的记忆、技能与知识资产 + Centralized management of agent memory, skills, and knowledge assets

@@ -319,16 +319,16 @@ export default function LoginGate({ {/* ====== 右侧登录表单面板 ====== */}
- Memory Hub + GODCALL - Memory Hub + GODCALL
-

欢迎回来

+

Welcome back

- 请选择记忆实例并输入你的 user_key 登录。 + Select a memory instance and enter your user_key to log in.

@@ -342,7 +342,7 @@ export default function LoginGate({ setError(null); }} disabled={submitting || instances.length === 0} - placeholder={instancesError ? '加载失败,请刷新重试' : '加载记忆实例中…'} + placeholder={instancesError ? 'Failed to load, please refresh and retry' : 'Loading memory instances…'} options={instances.map((inst) => ({ value: inst.instance_id, text: inst.name }))} /> @@ -357,13 +357,13 @@ export default function LoginGate({ setError(null); }} onKeyDown={onKeyDown} - placeholder="user_key,如 sk-mem-xxxxxxxxxxxxxxxx" + placeholder="user_key, e.g. sk-mem-xxxxxxxxxxxxxxxx" autoComplete="current-password" disabled={submitting} rules={false} />
- 请使用管理员为你分配的 user_key;若还没有,请联系团队管理员开号。 + Use the user_key assigned to you by an administrator. If you don't have one yet, contact your team administrator to get an account set up.
@@ -375,7 +375,7 @@ export default function LoginGate({ loading={submitting} disabled={submitting || !userKey.trim() || !instanceId} > - {submitting ? '登录中…' : '登录'} + {submitting ? 'Logging in…' : 'Log in'}
diff --git a/MemoryPanel/web/src/components/SettingsDialog.tsx b/MemoryPanel/web/src/components/SettingsDialog.tsx index 0c962bd..246a598 100644 --- a/MemoryPanel/web/src/components/SettingsDialog.tsx +++ b/MemoryPanel/web/src/components/SettingsDialog.tsx @@ -41,29 +41,29 @@ const RESOURCE_MODULES: ResourceModule[] = [ { id: 'wiki', paramKey: 'llm_wiki.enabled', - label: 'Wiki 知识库', - desc: '关闭后仅停止工具注入', + label: 'Wiki knowledge base', + desc: 'Disabling only stops tool injection', icon: , }, { id: 'code', paramKey: 'code_graph.enabled', label: 'Code_Graph', - desc: '关闭后仅停止工具注入', + desc: 'Disabling only stops tool injection', icon: , }, { id: 'skill', paramKey: 'skill.enabled', - label: 'Skill 技能', - desc: '关闭后工具注入与新技能抽取均停止', + label: 'Skill', + desc: 'Disabling stops both tool injection and new skill extraction', icon: , }, { id: 'chat_memory', paramKey: 'chat_memory.enabled', label: 'Chat_Memory', - desc: '关闭后工具注入与新对话写入均停止', + desc: 'Disabling stops both tool injection and new conversation writes', icon: , }, ]; @@ -122,19 +122,19 @@ export function SettingsDialog({ onClose }: { onClose: () => void }) { setError(''); try { await userConfigApi.setAssetCapability(mod.paramKey, next); - tea.notify.success(`${mod.label} 已${next ? '开启' : '关闭'}`); + tea.notify.success(`${mod.label} has been ${next ? 'enabled' : 'disabled'}`); } catch (e) { setEnabled((prev) => ({ ...prev, [mod.id]: previous })); const msg = e instanceof Error ? e.message : String(e); setError(msg); - tea.notify.error(`保存失败:${msg}`); + tea.notify.error(`Save failed: ${msg}`); } finally { setSavingKey(null); } } return ( - + {/* 当前只有「权限管理」一个 tab,历史上用 + 会渲染两条下划线 @@ -146,13 +146,13 @@ export function SettingsDialog({ onClose }: { onClose: () => void }) {
- 资源管理模块开关 + Resource module toggles - 开关按当前登录用户保存。关闭后,proxy 不会为该用户注入对应原子能力;变更对新会话即时生效。 + Toggles are saved per logged-in user. When disabled, the proxy will not inject the corresponding atomic capability for this user; changes take effect immediately for new sessions. {error && {error}} - {loading && 正在读取当前用户资源配置…} + {loading && Loading current user resource configuration…}
{RESOURCE_MODULES.map((mod) => ( @@ -181,11 +181,11 @@ export function SettingsDialog({ onClose }: { onClose: () => void }) { {mod.label} {savingKey === mod.paramKey ? ( - 保存中 + Saving ) : enabled[mod.id] ? ( - 已开启 + Enabled ) : ( - 已关闭 + Disabled )}
diff --git a/MemoryPanel/web/src/constants/menu.tsx b/MemoryPanel/web/src/constants/menu.tsx index e664529..6526166 100644 --- a/MemoryPanel/web/src/constants/menu.tsx +++ b/MemoryPanel/web/src/constants/menu.tsx @@ -39,18 +39,18 @@ export interface PageMeta { } export const PAGE_META: Record = { - workbench_board: { id: 'workbench_board', label: '任务看板', desc: 'Task 列表 / 创建 / 详情', group: '工作台', order: 0, affix: true }, - wiki: { id: 'wiki', label: 'Wiki 知识库', desc: '来源 / 图谱 / 页面 / 搜索', group: '资产管理', order: 2 }, - code: { id: 'code', label: 'Code_Graph', desc: '仓库 / 索引 / 搜索 / 探索', group: '资产管理', order: 3 }, - skills: { id: 'skills', label: 'Skill 技能', desc: '全部 / 团队池 / Agent 资产', group: '资产管理', order: 4 }, - chat_memory: { id: 'chat_memory', label: 'Chat_Memory', desc: 'L0–L3 分层记忆资产', group: '资产管理', order: 5 }, - team_members: { id: 'team_members', label: '成员管理', desc: 'Team 成员 / 用户 / 角色', group: '组织与权限', order: 0 }, - team_agents: { id: 'team_agents', label: 'Agents 管理', desc: 'Agent / 可配置范围 / 固定资产', group: '组织与权限', order: 1 }, - api_keys: { id: 'api_keys', label: 'API Key', desc: '管理你的 API Key,用于外部客户端接入', group: '组织与权限', order: 2 }, + workbench_board: { id: 'workbench_board', label: 'Task board', desc: 'Task list / create / details', group: 'Workbench', order: 0, affix: true }, + wiki: { id: 'wiki', label: 'Wiki knowledge base', desc: 'Sources / graph / pages / search', group: 'Assets', order: 2 }, + code: { id: 'code', label: 'Code_Graph', desc: 'Repos / index / search / explore', group: 'Assets', order: 3 }, + skills: { id: 'skills', label: 'Skills', desc: 'All / team pool / agent assets', group: 'Assets', order: 4 }, + chat_memory: { id: 'chat_memory', label: 'Chat_Memory', desc: 'L0-L3 layered memory assets', group: 'Assets', order: 5 }, + team_members: { id: 'team_members', label: 'Member management', desc: 'Team members / users / roles', group: 'Org & permissions', order: 0 }, + team_agents: { id: 'team_agents', label: 'Agent management', desc: 'Agents / configurable scope / fixed assets', group: 'Org & permissions', order: 1 }, + api_keys: { id: 'api_keys', label: 'API Key', desc: 'Manage your API keys for external client access', group: 'Org & permissions', order: 2 }, }; /** 分组排序顺序 */ -export const GROUP_ORDER = ['工作台', '组织与权限', '资产管理']; +export const GROUP_ORDER = ['Workbench', 'Org & permissions', 'Assets']; /** 每个页面在侧边栏菜单中的图标(Tea 官方图标,size 16) */ export const ITEM_ICON: Record = { @@ -66,7 +66,7 @@ export const ITEM_ICON: Record = { /** 分组图标(工作台 / 组织与权限 / 资产管理) */ export const GROUP_ICON: Record = { - 工作台: ( + 'Workbench': ( @@ -74,7 +74,7 @@ export const GROUP_ICON: Record = { ), - 组织与权限: ( + 'Org & permissions': ( @@ -82,7 +82,7 @@ export const GROUP_ICON: Record = { ), - 资产管理: ( + 'Assets': ( diff --git a/MemoryPanel/web/src/layouts/ConsoleLayout.tsx b/MemoryPanel/web/src/layouts/ConsoleLayout.tsx index 8630ec3..c90c864 100644 --- a/MemoryPanel/web/src/layouts/ConsoleLayout.tsx +++ b/MemoryPanel/web/src/layouts/ConsoleLayout.tsx @@ -107,7 +107,7 @@ export function ConsoleLayout() { for (const meta of Object.values(PAGE_META)) { // admin 角色 → 跳过所有「资源管理」分组下的项 - if (userRole === 'admin' && meta.group === '资源管理') continue; + if (userRole === 'admin' && meta.group === 'Resource management') continue; // reviewer → 跳过「成员管理」(member 可见,但新建/删除成员/Team 按钮在组件内按角色收敛) if (userRole === 'reviewer' && meta.id === 'team_members') continue; const list = byGroup.get(meta.group) ?? []; @@ -124,8 +124,8 @@ export function ConsoleLayout() { }, [userRole]); // 「工作台」分组只有任务看板一项,置顶展示为独立入口,不显示分组标题 - const pinnedGroup = menuGroups.find((g) => g.title === '工作台'); - const restGroups = menuGroups.filter((g) => g.title !== '工作台'); + const pinnedGroup = menuGroups.find((g) => g.title === 'Workbench'); + const restGroups = menuGroups.filter((g) => g.title !== 'Workbench'); const renderMenuItem = (item: PageMeta) => { const isActive = activePage === item.id; diff --git a/MemoryPanel/web/src/layouts/GlobalHeader/TeamSwitcher.tsx b/MemoryPanel/web/src/layouts/GlobalHeader/TeamSwitcher.tsx index f7de71f..a3953b8 100644 --- a/MemoryPanel/web/src/layouts/GlobalHeader/TeamSwitcher.tsx +++ b/MemoryPanel/web/src/layouts/GlobalHeader/TeamSwitcher.tsx @@ -68,14 +68,14 @@ export function TeamSwitcher({ userRole }: { userRole: TeamRole | null }) { {(active?.name ?? '?').slice(0, 1).toUpperCase()} - {active?.name ?? '选择 team'} - {active?.team_id ?? '未选择'} + {active?.name ?? 'Select a team'} + {active?.team_id ?? 'Not selected'} @@ -84,20 +84,20 @@ export function TeamSwitcher({ userRole }: { userRole: TeamRole | null }) { {(close) => (
-
切换团队
+
Switch team
- 不同团队的资产相互独立。切换后会在当前页面显示对应团队的数据。 + Assets are independent across teams. Switching will show that team's data on the current page.
-
团队({myTeams.length})
+
Teams ({myTeams.length})
{myTeams.length === 0 ? (
{userRole === 'admin' - ? '暂无 team。点击下方「新建团队」创建。' - : '你还没有被加入任何 team。请联系管理员将你加入团队。'} + ? 'No teams yet. Click "New team" below to create one.' + : "You haven't been added to any team yet. Contact an admin to be added."}
) : ( @@ -115,7 +115,7 @@ export function TeamSwitcher({ userRole }: { userRole: TeamRole | null }) { {t.name} - {t.members.length} 名成员 + {t.members.length} members {isActive && } @@ -133,22 +133,22 @@ export function TeamSwitcher({ userRole }: { userRole: TeamRole | null }) { size="full" value={newTeamName} onChange={setNewTeamName} - placeholder="团队名称(必填)" + placeholder="Team name (required)" />
- +
@@ -158,7 +158,7 @@ export function TeamSwitcher({ userRole }: { userRole: TeamRole | null }) { onClick={() => setShowCreateTeam(true)} > - 新建团队 + New team )}
diff --git a/MemoryPanel/web/src/layouts/GlobalHeader/index.tsx b/MemoryPanel/web/src/layouts/GlobalHeader/index.tsx index 4c0d3c4..5ea13b7 100644 --- a/MemoryPanel/web/src/layouts/GlobalHeader/index.tsx +++ b/MemoryPanel/web/src/layouts/GlobalHeader/index.tsx @@ -31,23 +31,23 @@ export function GlobalHeader({ {/* 左侧:品牌 + 团队切换器 */}
- Memory Hub - Memory Hub + GODCALL + GODCALL
{/* 右侧:同步状态 + 用户菜单 */}
- + - 实时同步 + Live sync
{profileOpen && currentUserId && ( - setProfileOpen(false)}> + setProfileOpen(false)}>
-
用户名
{currentUser}
+
Username
{currentUser}
User ID
{currentUserId}
- 发给团队管理员用于邀请你加入 Team + Share this with a team admin to be invited to a team
- +
)} diff --git a/MemoryPanel/web/src/lib/error-message.ts b/MemoryPanel/web/src/lib/error-message.ts index eb2982c..3dba95f 100644 --- a/MemoryPanel/web/src/lib/error-message.ts +++ b/MemoryPanel/web/src/lib/error-message.ts @@ -5,69 +5,69 @@ export interface ErrorEnvelopeLike { } const ERROR_CODE_MESSAGES: Record = { - UNAUTHORIZED: '登录状态已失效,请重新登录。', - INVALID_USER_KEY: '用户密钥无效或已失效,请重新登录。', - MISSING_USER_KEY: '缺少用户密钥,请重新登录。', - MISSING_INSTANCE_ID: '缺少实例信息,请重新选择实例后重试。', - INVALID_INSTANCE: '实例配置无效,请检查当前选择的实例。', - NOT_TEAM_MEMBER: '你不是该团队成员,无法执行此操作。', - PERMISSION_DENIED: '没有权限执行此操作。', - FORBIDDEN: '没有权限执行此操作。', - NOT_FOUND: '资源不存在或已被删除。', - ALREADY_EXISTS: '资源已存在,请勿重复创建。', - MEMBER_ALREADY_EXISTS: '该用户已是团队成员,无需重复添加。', - CONFLICT: '资源状态已变化,请刷新后重试。', - KERNEL_UNAVAILABLE: '内核服务不可用,请稍后重试。', - UPSTREAM_ERROR: '上游服务调用失败,请稍后重试。', - UNKNOWN_META_ACTION: '当前接口暂不支持,请刷新页面或联系管理员。', - NOT_IN_SCOPE: '该能力当前暂未开放。', + UNAUTHORIZED: 'Your session has expired. Please log in again.', + INVALID_USER_KEY: 'Your user key is invalid or has expired. Please log in again.', + MISSING_USER_KEY: 'Missing user key. Please log in again.', + MISSING_INSTANCE_ID: 'Missing instance information. Please reselect an instance and retry.', + INVALID_INSTANCE: 'Invalid instance configuration. Please check the currently selected instance.', + NOT_TEAM_MEMBER: 'You are not a member of this team and cannot perform this action.', + PERMISSION_DENIED: 'You do not have permission to perform this action.', + FORBIDDEN: 'You do not have permission to perform this action.', + NOT_FOUND: 'The resource does not exist or has been deleted.', + ALREADY_EXISTS: 'The resource already exists. Please do not create it again.', + MEMBER_ALREADY_EXISTS: 'This user is already a team member and does not need to be added again.', + CONFLICT: 'The resource state has changed. Please refresh and try again.', + KERNEL_UNAVAILABLE: 'The kernel service is unavailable. Please try again later.', + UPSTREAM_ERROR: 'The upstream service call failed. Please try again later.', + UNKNOWN_META_ACTION: 'This endpoint is not currently supported. Please refresh the page or contact an administrator.', + NOT_IN_SCOPE: 'This capability is not currently available.', - MISSING_TEAM_ID: '缺少团队信息,请重新选择团队。', - MISSING_AGENT_ID: '缺少 Agent 信息,请重新选择 Agent。', - AGENT_NOT_FOUND: 'Agent 不存在或已被删除。', - NOT_YOUR_AGENT: '只能操作你自己创建的 Agent。', - AGENT_NOT_IN_TEAM: '该 Agent 不属于当前团队。', - MISSING_TASK_ID: '缺少 Task 信息,请重新选择 Task。', + MISSING_TEAM_ID: 'Missing team information. Please reselect a team.', + MISSING_AGENT_ID: 'Missing agent information. Please reselect an agent.', + AGENT_NOT_FOUND: 'The agent does not exist or has been deleted.', + NOT_YOUR_AGENT: 'You can only operate on agents you created yourself.', + AGENT_NOT_IN_TEAM: 'This agent does not belong to the current team.', + MISSING_TASK_ID: 'Missing task information. Please reselect a task.', - MISSING_ASSET_ID: '缺少资产 ID。', - ASSET_NOT_FOUND: '资产不存在或已被删除。', - ASSET_NOT_SHARED: '该资产尚未共享到团队,不能分配给其它 Agent。', - ASSET_TYPE_MISMATCH: '资产类型不匹配,请刷新后重试。', - MISSING_BLOCK_ID: '缺少记忆资产信息。', - BLOCK_NOT_FOUND: '记忆资产不存在或已被删除。', - NOT_CHAT_MEMORY: '当前资产不是 Chat Memory。', - TEAM_MISMATCH: '资源不属于当前团队,请刷新后重试。', - INVALID_SCOPE: '可见范围无效。', + MISSING_ASSET_ID: 'Missing asset ID.', + ASSET_NOT_FOUND: 'The asset does not exist or has been deleted.', + ASSET_NOT_SHARED: 'This asset has not been shared with the team yet and cannot be assigned to other agents.', + ASSET_TYPE_MISMATCH: 'Asset type mismatch. Please refresh and try again.', + MISSING_BLOCK_ID: 'Missing memory asset information.', + BLOCK_NOT_FOUND: 'The memory asset does not exist or has been deleted.', + NOT_CHAT_MEMORY: 'This asset is not a Chat Memory.', + TEAM_MISMATCH: 'The resource does not belong to the current team. Please refresh and try again.', + INVALID_SCOPE: 'Invalid visibility scope.', - CANNOT_ALLOCATE_SELF_CHAT_MEMORY: '不能把该 Agent 自己的记忆再分配给自己。', - CANNOT_UNBIND_SELF_CHAT_MEMORY: '不能解绑 Agent 自己的记忆。', - ALREADY_ALLOCATED: '这条资产已经分配给该 Agent,无需重复分配。', - IMPORT_LIMIT_EXCEEDED: '该 Agent 最多只能借入 2 条其它 Agent 的记忆。', + CANNOT_ALLOCATE_SELF_CHAT_MEMORY: 'You cannot assign this agent\'s own memory back to itself.', + CANNOT_UNBIND_SELF_CHAT_MEMORY: 'You cannot unbind an agent\'s own memory.', + ALREADY_ALLOCATED: 'This asset has already been assigned to this agent and does not need to be assigned again.', + IMPORT_LIMIT_EXCEEDED: 'This agent can borrow at most 2 memories from other agents.', // 内核 canBindAsset/permission-checker 判定失败:常见场景是 asset 被 owner 切私密后 // 其他成员再对它做 read / bind / update 类操作。 - ASSET_PRIVATE_INACCESSIBLE: '该资产已被 owner 设为私密,你无权访问。', - ASSET_NOT_BINDABLE: '该资产的可见范围不允许绑定到此 Agent。请让 owner 将它设为团队可见后重试。', + ASSET_PRIVATE_INACCESSIBLE: 'This asset has been set to private by its owner. You do not have access to it.', + ASSET_NOT_BINDABLE: 'This asset\'s visibility scope does not allow it to be bound to this agent. Please ask the owner to make it team-visible and try again.', - INVALID_TITLE: '标题不能为空且不能超过长度限制。', - MISSING_MESSAGES: '缺少对话消息。', - TOO_MANY_MESSAGES: '一次最多导入 100 条消息。', - NO_VALID_MESSAGES: '没有可导入的有效消息。', + INVALID_TITLE: 'The title cannot be empty and must not exceed the length limit.', + MISSING_MESSAGES: 'Missing conversation messages.', + TOO_MANY_MESSAGES: 'You can import at most 100 messages at a time.', + NO_VALID_MESSAGES: 'There are no valid messages to import.', - MISSING_WIKI_ID: '缺少 Wiki 信息。', - WIKI_NOT_FOUND: 'Wiki 不存在或已被删除。', - WIKI_EMPTY_NO_SOURCES: 'Wiki 还没有上传源文件,请先上传 .md 文件后再抽取。', - MISSING_FILES: '请至少上传一个文件。', - TOO_MANY_FILES: '上传文件数量超过限制(最多 10 个),请分批上传。', - FILE_TOO_LARGE: '单个文件不能超过 512KB,请精简后再上传。', - TOTAL_TOO_LARGE: '单次上传总量不能超过 5MB,请分批上传。', - MISSING_CODE_GRAPH_ID: '缺少 CodeGraph 信息。', - CODE_GRAPH_NOT_FOUND: 'CodeGraph 不存在或已被删除。', - KNOWLEDGE_NOT_FOUND: '知识库资源不存在或已被删除。', + MISSING_WIKI_ID: 'Missing wiki information.', + WIKI_NOT_FOUND: 'The wiki does not exist or has been deleted.', + WIKI_EMPTY_NO_SOURCES: 'This wiki has no uploaded source files yet. Please upload .md files before extracting.', + MISSING_FILES: 'Please upload at least one file.', + TOO_MANY_FILES: 'Too many files uploaded (max 10). Please upload in batches.', + FILE_TOO_LARGE: 'A single file cannot exceed 512KB. Please trim it down before uploading.', + TOTAL_TOO_LARGE: 'The total upload size cannot exceed 5MB. Please upload in batches.', + MISSING_CODE_GRAPH_ID: 'Missing CodeGraph information.', + CODE_GRAPH_NOT_FOUND: 'The CodeGraph does not exist or has been deleted.', + KNOWLEDGE_NOT_FOUND: 'The knowledge base resource does not exist or has been deleted.', - INVALID_ARGUMENT: '请求参数不正确,请检查输入后重试。', - VALIDATION_ERROR: '请求参数不正确,请检查输入后重试。', - RATE_LIMITED: '请求过于频繁,请稍后重试。', - INTERNAL_ERROR: '服务内部错误,请稍后重试。', + INVALID_ARGUMENT: 'The request parameters are invalid. Please check your input and try again.', + VALIDATION_ERROR: 'The request parameters are invalid. Please check your input and try again.', + RATE_LIMITED: 'Too many requests. Please try again later.', + INTERNAL_ERROR: 'Internal server error. Please try again later.', }; const MESSAGE_PATTERNS: Array<[RegExp, string]> = [ @@ -79,9 +79,9 @@ const MESSAGE_PATTERNS: Array<[RegExp, string]> = [ // 注:asset_not_bindable / visibility_restricted 在 PRIORITY_MESSAGE_PATTERNS 里前置匹配, // 因为它们会被 permission_denied 前缀吞掉。 [/permission[_\s-]?denied/i, ERROR_CODE_MESSAGES.PERMISSION_DENIED], - [/fetch failed|networkerror|failed to fetch/i, '网络请求失败,请检查服务是否可用后重试。'], - [/timeout|aborted/i, '请求超时,请稍后重试。'], - [/empty .* response/i, '服务返回为空,请稍后重试。'], + [/fetch failed|networkerror|failed to fetch/i, 'Network request failed. Please check whether the service is available and try again.'], + [/timeout|aborted/i, 'The request timed out. Please try again later.'], + [/empty .* response/i, 'The service returned an empty response. Please try again later.'], [/internal server error/i, ERROR_CODE_MESSAGES.INTERNAL_ERROR], ]; @@ -182,7 +182,7 @@ export function formatApiErrorMessage(input: { if (input.httpStatus === 403) return ERROR_CODE_MESSAGES.PERMISSION_DENIED; if (input.httpStatus === 404) return ERROR_CODE_MESSAGES.NOT_FOUND; if (input.httpStatus && input.httpStatus >= 500) return ERROR_CODE_MESSAGES.INTERNAL_ERROR; - return input.fallback ?? '操作失败,请稍后重试。'; + return input.fallback ?? 'Operation failed. Please try again later.'; } export function getErrorMessage(err: unknown): string { @@ -207,5 +207,5 @@ export function getErrorMessage(err: unknown): string { } if (err instanceof Error) return formatApiErrorMessage({ message: err.message, fallback: err.message }); if (typeof err === 'string') return formatApiErrorMessage({ message: err, fallback: err }); - return '操作失败,请稍后重试。'; + return 'Operation failed. Please try again later.'; } diff --git a/MemoryPanel/web/src/lib/knowledge-api.ts b/MemoryPanel/web/src/lib/knowledge-api.ts index c7268f2..facc51f 100644 --- a/MemoryPanel/web/src/lib/knowledge-api.ts +++ b/MemoryPanel/web/src/lib/knowledge-api.ts @@ -258,17 +258,17 @@ async function listAgentFixedKnowledge(agentId: string): Promise = { - scanning: '扫描源文档', - ingesting: '抽取文档内容', - 'rebuilding-index': '重建索引', + scanning: 'Scanning source documents', + ingesting: 'Extracting document content', + 'rebuilding-index': 'Rebuilding index', }; - return internalStatus ? (map[internalStatus] ?? internalStatus) : '加工中'; + return internalStatus ? (map[internalStatus] ?? internalStatus) : 'Processing'; } export function wikiProgressPercent(status: WikiDetail['status'], internalStatus?: string | null): number { @@ -321,7 +321,7 @@ export const knowledgeApi = { /** 触发 ingest 后轮询 wiki/get,用真实 status/internal_status 驱动进度展示。 */ ingestWithPolling: async (wikiId: string, callbacks: IngestStreamCallbacks, _teamId: string): Promise => { try { - callbacks.onProgress?.({ type: 'file_start', detail: '正在触发抽取...', done: 0, total: 100, ts: Date.now() }); + callbacks.onProgress?.({ type: 'file_start', detail: 'Triggering extraction...', done: 0, total: 100, ts: Date.now() }); try { await knowledgeApi.wiki.ingest(wikiId); } catch (err: any) { @@ -335,27 +335,27 @@ export const knowledgeApi = { const detail = await knowledgeApi.wiki.get(wikiId); const stage = wikiStageLabel(detail.status, detail.internal_status); const done = wikiProgressPercent(detail.status, detail.internal_status); - const pageHint = typeof detail.page_count === 'number' ? `,当前 ${detail.page_count} 页` : ''; + const pageHint = typeof detail.page_count === 'number' ? `, ${detail.page_count} pages so far` : ''; callbacks.onProgress?.({ type: 'file_done', - detail: `第 ${attempt} 次检查:${stage}${pageHint}`, + detail: `Check ${attempt}: ${stage}${pageHint}`, done, total: 100, ts: Date.now(), }); if (detail.status === 'ready') { - callbacks.onProgress?.({ type: 'batch_done', detail: '抽取完成', done: 100, total: 100, ts: Date.now() }); + callbacks.onProgress?.({ type: 'batch_done', detail: 'Extraction complete', done: 100, total: 100, ts: Date.now() }); const count = detail.page_count ?? 0; callbacks.onComplete?.({ total: count, ingested: count }); return; } if (detail.status === 'failed') { - callbacks.onError?.(detail.sync_error || '抽取失败'); + callbacks.onError?.(detail.sync_error || 'Extraction failed'); return; } } - callbacks.onError?.('抽取超时,请稍后刷新查看最新状态'); + callbacks.onError?.('Extraction timed out. Refresh later to see the latest status.'); } catch (err: any) { callbacks.onError?.(err.message || String(err)); } @@ -381,7 +381,7 @@ export const knowledgeApi = { '/wiki/page/read', { wiki_id: wikiId, refs: [path] } ); const item = d.items?.[0]; - if (item?.not_found) throw new Error(`页面不存在: ${path}`); + if (item?.not_found) throw new Error(`Page not found: ${path}`); return { content: item?.content ?? '' }; }, @@ -498,7 +498,7 @@ export async function pollWikiStatus(wikiId: string, maxAttempts = 30, intervalM if (detail.status === 'ready' || detail.status === 'failed') return detail; await new Promise(r => setTimeout(r, intervalMs)); } - throw new Error(`Wiki ${wikiId} ingest 超时`); + throw new Error(`Wiki ${wikiId} ingest timed out`); } /** 轮询 code-graph sync 状态 */ @@ -508,5 +508,5 @@ export async function pollCodeGraphStatus(codeGraphId: string, maxAttempts = 30, if (detail.status === 'ready' || detail.status === 'failed') return detail; await new Promise(r => setTimeout(r, intervalMs)); } - throw new Error(`CodeGraph ${codeGraphId} sync 超时`); + throw new Error(`CodeGraph ${codeGraphId} sync timed out`); } diff --git a/MemoryPanel/web/src/lib/skill-api.ts b/MemoryPanel/web/src/lib/skill-api.ts index ed09ab3..abe0502 100644 --- a/MemoryPanel/web/src/lib/skill-api.ts +++ b/MemoryPanel/web/src/lib/skill-api.ts @@ -117,7 +117,7 @@ async function skillCall(action: string, body: Record): Prom body: JSON.stringify(stripEmpty(body)), }); if (res.status === 401) { - throw new SkillApiError(401, 'Unauthorized - 用户登录已失效或缺少用户密钥', ''); + throw new SkillApiError(401, 'Unauthorized - your session has expired or is missing a user key', ''); } const text = await res.text(); let envelope: SkillEnvelope; diff --git a/MemoryPanel/web/src/lib/tea-bridge.ts b/MemoryPanel/web/src/lib/tea-bridge.ts index a0e66b2..3949078 100644 --- a/MemoryPanel/web/src/lib/tea-bridge.ts +++ b/MemoryPanel/web/src/lib/tea-bridge.ts @@ -53,8 +53,8 @@ export const tea = { return Modal.confirm({ message: opts.message, description: opts.description, - okText: opts.okText ?? '确认', - cancelText: opts.cancelText ?? '取消', + okText: opts.okText ?? 'Confirm', + cancelText: opts.cancelText ?? 'Cancel', }); }, @@ -87,7 +87,7 @@ export const tea = { : `request_id: ${input.requestId}` : input.detail; notification.error({ - title: input.title ?? '操作失败', + title: input.title ?? 'Operation failed', description: desc, }); return; @@ -99,13 +99,13 @@ export const tea = { ? `${friendly}\nrequest_id: ${requestId}` : friendly; notification.error({ - title: '操作失败', + title: 'Operation failed', description: desc, }); }, warning: (msg: string) => notification.warning({ - title: '提示', + title: 'Notice', description: msg, }), info: (msg: string) => @@ -129,9 +129,9 @@ export const tea = { */ confirmDelete: (name: string, detail?: string) => Modal.confirm({ - message: `确认删除「${name}」?`, - description: detail ?? '删除后不可恢复。', - okText: '删除', - cancelText: '取消', + message: `Delete "${name}"?`, + description: detail ?? 'This cannot be undone.', + okText: 'Delete', + cancelText: 'Cancel', }), }; diff --git a/MemoryPanel/web/src/pages/ResourcePage/components/AdminResourceLock.tsx b/MemoryPanel/web/src/pages/ResourcePage/components/AdminResourceLock.tsx index ecf41c3..352d17d 100644 --- a/MemoryPanel/web/src/pages/ResourcePage/components/AdminResourceLock.tsx +++ b/MemoryPanel/web/src/pages/ResourcePage/components/AdminResourceLock.tsx @@ -14,9 +14,9 @@ export function AdminResourceLock() { -
资源管理功能暂未对管理员开放
+
Resource management is not yet available to admins
- Admin 账号当前仅用于组织管理(新建 Team、新增成员)。资源管理请使用普通成员账号操作。 + Admin accounts are currently for organization management only (creating teams, adding members). Use a regular member account for resource management.
diff --git a/MemoryPanel/web/src/pages/ResourcePage/components/AllocateAssetDialog.tsx b/MemoryPanel/web/src/pages/ResourcePage/components/AllocateAssetDialog.tsx index 727e3b4..793ea30 100644 --- a/MemoryPanel/web/src/pages/ResourcePage/components/AllocateAssetDialog.tsx +++ b/MemoryPanel/web/src/pages/ResourcePage/components/AllocateAssetDialog.tsx @@ -22,7 +22,7 @@ export type AllocateAssetType = 'skill' | 'llm_wiki' | 'code_graph' | 'chat_memo const TYPE_LABEL: Record = { skill: 'Skill', llm_wiki: 'Wiki', - code_graph: '代码图谱', + code_graph: 'Code graph', chat_memory: 'Memory' }; @@ -49,14 +49,14 @@ export default function AllocateAssetDialog(props: { async function submit(): Promise { if (!agentId) { - setError('请选择 agent。'); + setError('Please select an agent.'); return; } setError(null); setSubmitting(true); try { await props.onAllocate(agentId); - tea.notify.success(`已分配「${props.assetLabel}」→ ${agentId}`); + tea.notify.success(`Allocated "${props.assetLabel}" to ${agentId}`); props.onClose(); } catch (err) { tea.notify.error(err); @@ -66,15 +66,15 @@ export default function AllocateAssetDialog(props: { } return ( - +
{props.team && ( - + {props.team.name} {props.team.team_id} )} - + {props.assetLabel} @@ -82,7 +82,7 @@ export default function AllocateAssetDialog(props: { size="full" value={agentId} onChange={setAgentId} - placeholder={props.agents.length === 0 ? '(暂无 agent)' : '请选择 agent'} + placeholder={props.agents.length === 0 ? '(No agents yet)' : 'Select an agent'} options={props.agents.map((a) => ({ value: a.id, text: `${a.id} · ${a.name}` }))} disabled={props.agents.length === 0} /> @@ -91,8 +91,8 @@ export default function AllocateAssetDialog(props: {
- - + +
); diff --git a/MemoryPanel/web/src/pages/ResourcePage/components/AssetScopeManager.tsx b/MemoryPanel/web/src/pages/ResourcePage/components/AssetScopeManager.tsx index 45b147f..3becf45 100644 --- a/MemoryPanel/web/src/pages/ResourcePage/components/AssetScopeManager.tsx +++ b/MemoryPanel/web/src/pages/ResourcePage/components/AssetScopeManager.tsx @@ -42,8 +42,8 @@ export interface AssetScopeItem { } const SCOPE_OPTIONS: Array<{ value: AssetConfigScope; label: string }> = [ - { value: 'team', label: '团队内可配置' }, - { value: 'private', label: '仅自己私有' } + { value: 'team', label: 'Configurable by team' }, + { value: 'private', label: 'Private (owner only)' } ]; export default function AssetScopeManager({ @@ -68,22 +68,22 @@ export default function AssetScopeManager({ return (
- {label} · 可配置范围 + {label} · Configurable scope
- 每个 owner 可以管理自己的 {label}:选择 + Each owner can manage their own {label}: choose - 团队内可配置 + Configurable by team - (团队成员都能改)或 + (any team member can change it) or - 仅自己私有 + Private (owner only) - (只有你能改)。只有资产 owner 与团队管理员可切换。 + (only you can change it). Only the asset owner and team admins can switch this.
{items.length === 0 ? ( -
当前团队下还没有 {label} 资产。
+
No {label} assets in the current team yet.
) : (
    {items.map((item) => { @@ -103,10 +103,10 @@ export default function AssetScopeManager({ {effectiveOwner ? ( owner @{effectiveOwner} - {ownerIsMe && (你)} + {ownerIsMe && (you)} ) : ( - 无归属 + Unowned )}
{item.meta && ( @@ -127,7 +127,7 @@ export default function AssetScopeManager({ /> ) : ( - {scope === 'private' ? '仅自己私有' : '团队内可配置'} + {scope === 'private' ? 'Private (owner only)' : 'Configurable by team'} )} diff --git a/MemoryPanel/web/src/pages/code/CodePage/components/CodeSourcesPanel.tsx b/MemoryPanel/web/src/pages/code/CodePage/components/CodeSourcesPanel.tsx index 24846b5..24cfd4d 100644 --- a/MemoryPanel/web/src/pages/code/CodePage/components/CodeSourcesPanel.tsx +++ b/MemoryPanel/web/src/pages/code/CodePage/components/CodeSourcesPanel.tsx @@ -71,8 +71,8 @@ function isValidGitHttpUrl(raw: string): boolean { type ScopeTab = 'team' | 'fixed'; const SCOPE_LABELS: Record = { - team: '团队 Code 池', - fixed: 'Agent 资产', + team: 'Team code pool', + fixed: 'Agent assets', }; /** @@ -84,7 +84,7 @@ function CodeOwnerLabel({ userId, currentUserId }: { userId: string; currentUser return ( @{name || userId} - {userId === currentUserId && (你)} + {userId === currentUserId && (you)} ); } @@ -92,18 +92,18 @@ function CodeOwnerLabel({ userId, currentUserId }: { userId: string; currentUser // 状态 → Tea Tag 语义主题映射(soft 变体),对齐 Memory 的 statusTheme。 function statusLabel(s: string) { const map: Record = { - ready: ['就绪', 'success'], - pending: ['排队中', 'warning'], - processing: ['构建中', 'warning'], - failed: ['失败', 'error'], - cloning: ['克隆中', 'warning'], - indexing: ['索引中', 'warning'], - syncing: ['同步中', 'warning'], - error: ['错误', 'error'], - missing: ['已丢失', 'error'], + ready: ['Ready', 'success'], + pending: ['Queued', 'warning'], + processing: ['Building', 'warning'], + failed: ['Failed', 'error'], + cloning: ['Cloning', 'warning'], + indexing: ['Indexing', 'warning'], + syncing: ['Syncing', 'warning'], + error: ['Error', 'error'], + missing: ['Lost', 'error'], }; const [label, theme] = map[s] ?? [s, 'default']; - const hint = (s === 'pending' || s === 'processing') ? ' · 可能需要数分钟' : ''; + const hint = (s === 'pending' || s === 'processing') ? ' · may take a few minutes' : ''; return {label}{hint}; } @@ -161,7 +161,7 @@ export default function CodeSourcesPanel() { const items = await knowledgeApi.code.agentFixed(agentFilter); setFixedBoundIds(new Set(items.map((it) => it.knowledge_id))); } catch (e: any) { - tea.notify.error(e?.message || '加载固定资产失败'); + tea.notify.error(e?.message || 'Failed to load fixed assets'); setFixedBoundIds(new Set()); } }, [agentFilter]); @@ -287,7 +287,7 @@ export default function CodeSourcesPanel() { // (callback S2S 是主力,这里只是兜底,但失败要可见) const msg = e?.message || String(e); if (!/already|exist|409|registered|ok/i.test(msg)) { - tea.notify.error(`注册 meta 失败: ${msg}`); + tea.notify.error(`Failed to register meta: ${msg}`); } } toRemove.push(detail.code_graph_id); @@ -323,19 +323,19 @@ export default function CodeSourcesPanel() { async function handleUnbindCode(codeGraphId: string) { if (!agentFilter) return; const ok = await tea.confirm({ - message: '确认解绑该代码图谱?', - description: '将从当前 agent 移除该代码图谱绑定。', - okText: '解绑', + message: 'Unbind this code graph?', + description: 'This removes the code graph binding from the current agent.', + okText: 'Unbind', }); if (!ok) return; try { await knowledgeApi.code.unbind(codeGraphId, agentFilter); - tea.notify.success('已解绑'); + tea.notify.success('Unbound'); if (selectedCodeAsset?.cgId === codeGraphId) setSelectedCodeAsset(null); await fetchFixedBindings(); await fetchSources(); } catch (e: any) { - tea.notify.error(e?.message || '解绑失败'); + tea.notify.error(e?.message || 'Unbind failed'); } } @@ -344,7 +344,7 @@ export default function CodeSourcesPanel() { if (!repo || !formBranch.trim() || !activeTeamId) return; // 防御性校验:按钮已按 validUrl 禁用,这里再挡一层防止绕过 if (!isValidGitHttpUrl(repo)) { - tea.notify.error('请输入合法的 HTTPS Git 仓库地址,且必须以 .git 结尾(如 https://gitlab.example.com/namespace/repo.git),不能含空格。'); + tea.notify.error('Enter a valid HTTPS git repository URL ending in .git (e.g. https://gitlab.example.com/namespace/repo.git), with no spaces.'); return; } setSubmitting(true); @@ -353,7 +353,7 @@ export default function CodeSourcesPanel() { setShowRegister(false); setFormRepo(''); setFormBranch('main'); setScopeTab('team'); setInFlight((prev) => [...prev.filter((x) => x.code_graph_id !== detail.code_graph_id), detail]); - tea.notify.info('仓库已注册,正在构建代码图谱,可能需要数分钟'); + tea.notify.info('Repository registered. Building the code graph — this may take a few minutes.'); fetchSources(); } catch (e: any) { tea.notify.error(e); } finally { setSubmitting(false); } @@ -368,8 +368,8 @@ export default function CodeSourcesPanel() { const source = sources.find(s => s.code_graph_id === cgId); if (!source) return; const ok = await tea.confirm({ - message: `确定要删除仓库「${source.repo_name || source.repo_url} (${source.branch})」吗?`, - okText: '删除', + message: `Delete repository "${source.repo_name || source.repo_url} (${source.branch})"?`, + okText: 'Delete', }); if (!ok) return; try { @@ -381,7 +381,7 @@ export default function CodeSourcesPanel() { setInFlight((prev) => prev.filter((x) => x.code_graph_id !== cgId)); if (selectedCodeAsset?.cgId === cgId) setSelectedCodeAsset(null); if (selectedCgId === cgId) setSubView('list'); - tea.notify.success('已删除'); + tea.notify.success('Deleted'); fetchSources(); } catch (e: any) { tea.notify.error(e); } }; @@ -447,14 +447,14 @@ export default function CodeSourcesPanel() {
{selRepo} - 分支 {selBranch} + Branch {selBranch} {selected?.commit_hash && @ {selected.commit_hash}} {selected && statusLabel(selected.status)} {selected?.last_sync_at && {new Date(selected.last_sync_at).toLocaleString()}}
@@ -466,22 +466,22 @@ export default function CodeSourcesPanel() { {/* 统计 */} {selected?.stats && (
- - - + + +
)} {/* 仓库信息 */} {selected && ( - +
Code Graph ID {selected.code_graph_id} Git URL {selected.repo_url || '—'} - 最后同步 + Last synced {selected.last_sync_at ? new Date(selected.last_sync_at).toLocaleString() : '—'}
@@ -490,9 +490,9 @@ export default function CodeSourcesPanel() { {/* 代码搜索 */} - + - 按符号名快速定位,只返回匹配的函数 / 类 / 变量所在的文件与行号,不含代码原文。适合"这个符号在哪里"。 + Look up by symbol name — returns only the file and line number of matching functions/classes/variables, not the source itself. Good for "where is this symbol".
setSearchQuery(v)} onSearch={() => void handleSearch()} - placeholder="输入符号名(函数 / 类 / 变量),返回其所在位置…" + placeholder="Enter a symbol name (function/class/variable) to find its location..." />
{searching && } @@ -514,9 +514,9 @@ export default function CodeSourcesPanel() { {/* 代码探索 */} - + - 一次返回相关文件的完整原文与调用关系,让 AI 直接拿到上下文,无需再逐个 grep / 读文件。适合"这个功能是怎么实现的"。 + Returns the full source of related files along with call relationships in one go, giving the AI direct context without grepping or reading files one by one. Good for "how this feature is implemented".
setExploreQuery(v)} onSearch={() => void handleExplore()} - placeholder="用自然语言或符号名描述要理解的功能 / 流程,返回相关文件原文…" + placeholder="Describe the feature or flow you want to understand, in natural language or a symbol name..." />
{exploring && } @@ -558,18 +558,18 @@ export default function CodeSourcesPanel() { value={agentFilter} onChange={setAgentFilter} disabled={teamAgents.length === 0} - placeholder="无可选 Agent" - options={teamAgents.map((agent) => ({ value: agent.id, text: `${agent.name}(${agent.id})` }))} + placeholder="No agent available" + options={teamAgents.map((agent) => ({ value: agent.id, text: `${agent.name} (${agent.id})` }))} /> ) : undefined} - subtitle={activeTeam ? `${activeTeam.name} · 共 ${stats.total} 个仓库` : `共 ${stats.total} 个仓库`} + subtitle={activeTeam ? `${activeTeam.name} · ${stats.total} repositories total` : `${stats.total} repositories total`} actions={scopeTab !== 'fixed' ? ( ) : undefined} /> @@ -577,29 +577,29 @@ export default function CodeSourcesPanel() {
- - - - + + + +
setShowRegister(true)}>+ 注册仓库} + left={} right={(
setStatusFilter(value as StatusFilter)} options={[ - { value: 'all', text: '全部状态' }, - { value: 'ready', text: '就绪' }, - { value: 'processing', text: '处理中' }, - { value: 'error', text: '异常' }, + { value: 'all', text: 'All statuses' }, + { value: 'ready', text: 'Ready' }, + { value: 'processing', text: 'Processing' }, + { value: 'error', text: 'Error' }, ]} /> - 暂无已注册仓库 - 点击上方“+ 注册仓库”注册第一个 + No registered repositories + Click "+ Register repository" above to register your first one
)} /> ) : filteredSources.length === 0 ? ( - + ) : viewMode === 'card' ? (
{filteredSources.map((source) => { @@ -645,7 +645,7 @@ export default function CodeSourcesPanel() { type="button" className="_codelist-card-head _codelist-card-name-trigger" onClick={(event) => { event.stopPropagation(); openDetail(source.code_graph_id); }} - title={`查看 ${repoLabel} 详情`} + title={`View ${repoLabel} details`} > {repoLabel} @@ -653,7 +653,7 @@ export default function CodeSourcesPanel() {
{statusLabel(source.status)} - 分支 {source.branch} + Branch {source.branch} {source.commit_hash && @ {source.commit_hash}} {source.stats && {source.stats.nodes.toLocaleString()} nodes · {source.stats.files.toLocaleString()} files} {formatShortTime(source.last_sync_at)} @@ -661,26 +661,26 @@ export default function CodeSourcesPanel() {
{scopeTab === 'fixed' ? ( - `固定资产 · ${agentFilter || '未选择 Agent'}` + `Fixed asset · ${agentFilter || 'No agent selected'}` ) : source.owner_user_id ? ( ) : ( - '团队 Code 池' + 'Team code pool' )}
-
ID:{source.code_graph_id}
+
ID: {source.code_graph_id}
event.stopPropagation()}> {scopeTab === 'fixed' ? ( ) : ( - + )} - -
@@ -696,14 +696,14 @@ export default function CodeSourcesPanel() { columns={[ { key: 'repo_name', - header: '仓库', + header: 'Repository', width: 250, render: (source) => ( + ) : ( - + )} - - + +
); }, @@ -792,10 +792,10 @@ export default function CodeSourcesPanel() { // 已输入内容、非 SSH、但又不是合法 http(s) 地址 → 提示格式错误。 const showUrlError = !!trimmedRepo && !isSsh && !validUrl; return ( - setShowRegister(false)} disableEscape={submitting}> + setShowRegister(false)} disableEscape={submitting}>
- + {isSsh && ( - 当前版本不支持 SSH 格式的仓库地址,请改用 HTTPS 格式(如 https://gitlab.example.com/namespace/repo.git)。 + The current version doesn't support SSH-style repository URLs. Use HTTPS instead (e.g. https://gitlab.example.com/namespace/repo.git). )} {showUrlError && ( - 请输入合法的 HTTP(S) Git 仓库地址,且必须以 .git 结尾(如 https://gitlab.example.com/namespace/repo.git),不能含空格。 + Enter a valid HTTP(S) git repository URL ending in .git (e.g. https://gitlab.example.com/namespace/repo.git), with no spaces. )} - +
- +
); @@ -833,9 +833,9 @@ export default function CodeSourcesPanel() { team={activeTeam ? { team_id: activeTeam.team_id, name: activeTeam.name } : null} onClose={() => setAllocateTarget(null)} onAllocate={async (agentId) => { - if (!activeTeamId) throw new Error('请先选择 team'); + if (!activeTeamId) throw new Error('Select a team first'); await knowledgeApi.code.allocate(activeTeamId, allocateTarget.cgId, agentId); - tea.notify.success('已分配到 Agent'); + tea.notify.success('Assigned to agent'); await fetchSources(); if (scopeTab === 'fixed') await fetchFixedBindings(); }} diff --git a/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/AllocateMemoryDialog.tsx b/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/AllocateMemoryDialog.tsx index 09ddd27..7857f40 100644 --- a/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/AllocateMemoryDialog.tsx +++ b/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/AllocateMemoryDialog.tsx @@ -48,37 +48,37 @@ export function AllocateMemoryDialog({ // 文案分支:不同来源说不同的话,避免"团队池里"这种错误措辞出现在 personal tab。 const description = memorySource === 'team' ? ( <> - 把团队池里的记忆块 {memoryTitle} 绑定到所选 agent 的固定资产。 + Bind the memory block {memoryTitle} from the team pool to the selected agent's fixed assets. ) : ( <> - 把记忆块 {memoryTitle} 分配到所选 agent 的固定资产。 + Allocate the memory block {memoryTitle} to the selected agent's fixed assets. ); return ( - +
- {description} + {description} {agents.length === 0 ? ( - 没有可分配的 Agent。可能的原因: -
· 你还没有创建任何 Agent —— 请先到「Agent 管理」创建一个。 -
· 选中的记忆块是你某个 Agent 的自有记忆,不能分配给同一个 Agent(规则:不能把 Agent 自己的记忆再分配给自己)。 -
· 该记忆块已绑定到你所有的 Agent,无需重复分配。 + No agents available to allocate to. Possible reasons: +
· You haven't created any agents yet — create one in "Agent management" first. +
· The selected memory block is an agent's own memory and can't be allocated to that same agent (an agent's own memory can't be allocated back to itself). +
· This memory block is already bound to all of your agents, so no further allocation is needed.
) : ( - ({ value: a.agent_id, text: `${a.name}(${a.agent_id})` }))} /> + options={agents.map((a) => ({ value: a.agent_id, text: `${a.name} (${a.agent_id})` }))} /> )}
@@ -113,11 +113,11 @@ export function ImportBlockDialog({ {/* 格式说明 */}
-
支持 [{`{role, content}`}] 格式的 JSON 数组:
+
Supports a JSON array in the [{`{role, content}`}] format:
    -
  • role 取值:"user""assistant"
  • -
  • content:消息正文(字符串,非空)
  • -
  • 单次最多 {MAX_MESSAGES}
  • +
  • role is either "user" or "assistant"
  • +
  • content: message body (non-empty string)
  • +
  • Up to {MAX_MESSAGES} messages per import
@@ -125,12 +125,12 @@ export function ImportBlockDialog({ {/* 导入方式切换 */}
setImportMode(v as 'paste' | 'file')} - options={[{ value: 'paste', text: (<> 粘贴文本) }, { value: 'file', text: (<> 导入 JSON 文件) }]} /> + options={[{ value: 'paste', text: (<> Paste text) }, { value: 'file', text: (<> Import JSON file) }]} />
{importMode === 'paste' ? ( - + ) : ( - - - {fileName && 已选择:{fileName}} + + + {fileName && Selected: {fileName}} {sessionPayload && ( - +
-                  {sessionPayload.slice(0, 2000)}{sessionPayload.length > 2000 ? '\n…(已截断)' : ''}
+                  {sessionPayload.slice(0, 2000)}{sessionPayload.length > 2000 ? '\n…(truncated)' : ''}
                 
)} @@ -159,7 +159,7 @@ export function ImportBlockDialog({ {/* 解析结果反馈 */} {sessionPayload.trim() && parsed.ok && ( - 解析成功 · 共 {parsed.messages.length} 条消息({parsed.messages.filter(m => m.role === 'user').length} user / {parsed.messages.filter(m => m.role === 'assistant').length} assistant) + Parsed successfully · {parsed.messages.length} messages total ({parsed.messages.filter(m => m.role === 'user').length} user / {parsed.messages.filter(m => m.role === 'assistant').length} assistant) )} {sessionPayload.trim() && !parsed.ok && ( @@ -172,11 +172,11 @@ export function ImportBlockDialog({ disabled={!canSubmit} loading={submitting} onClick={submit} - title={!scopeAgentId ? '请先选择归属 agent' : !parsed.ok ? parsed.error : ''} + title={!scopeAgentId ? 'Select an owning agent first' : !parsed.ok ? parsed.error : ''} > - {submitting ? '导入中…' : '导入记忆'} + {submitting ? 'Importing...' : 'Import memory'} - + ); diff --git a/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/PersonalAssetsTable.tsx b/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/PersonalAssetsTable.tsx index bffbf7e..af97f57 100644 --- a/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/PersonalAssetsTable.tsx +++ b/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/PersonalAssetsTable.tsx @@ -22,9 +22,9 @@ function MemoryOwnerTag({ userId, isCurrentUser }: { userId: string; isCurrentUs const displayName = useUserDisplayName(userId); return ( - + {displayName || userId} - {isCurrentUser && '(你)'} + {isCurrentUser && ' (you)'} ); @@ -53,20 +53,20 @@ export function PersonalAssetsTable({ {/* 顶部 */}
- 我的资产分配 + My asset allocations - 新建 Agent 时自动生成的记忆默认私密,切换为「共享」后团队内其他成员可见(只读) + Memories auto-generated for a new agent default to private; switch to "Shared" to make them visible (read-only) to other team members
{loading ? (
- 加载中… + Loading...
) : blocks.length === 0 ? (
- 暂无记忆资产 · 创建一个 Agent 后会自动生成一条属于它的私密记忆 + No memory assets yet · creating an agent automatically generates a private memory for it
) : ( @@ -88,7 +88,7 @@ export function PersonalAssetsTable({
{block.title}
- 更新时间:{new Date(block.updated_at_ms).toLocaleString()} + Updated: {new Date(block.updated_at_ms).toLocaleString()}
{block.id}
{block.uploaded_by_user_id && ( @@ -96,11 +96,11 @@ export function PersonalAssetsTable({ {isTeam ? ( - 共享 + Shared ) : ( - 私密 + Private )}
@@ -114,16 +114,16 @@ export function PersonalAssetsTable({
diff --git a/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/constants.ts b/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/constants.ts index af115e4..38e1ddf 100644 --- a/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/constants.ts +++ b/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/constants.ts @@ -4,16 +4,16 @@ export const PROSE_CLASS = 'prose prose-sm prose-slate max-w-none prose-headings:my-2 prose-p:my-1.5 prose-ul:my-1.5 prose-ol:my-1.5 prose-pre:my-1.5'; export const LAYERS: LayerMeta[] = [ - { id: 'L0', label: 'L0 · 对话原文', short: '对话原文', desc: '原始对话 / 工具调用流水,不做压缩', tone: 'default' }, - { id: 'L1', label: 'L1 · 原子记忆', short: '原子记忆', desc: '从原文抽取出来的最小事实 / 约束', tone: 'brand' }, - { id: 'L2', label: 'L2 · 场景记忆', short: '场景记忆', desc: '围绕场景聚合的多条原子记忆总结', tone: 'success' }, - { id: 'L3', label: 'L3 · 核心记忆', short: '核心记忆', desc: '沉淀的核心准则 / 模板 / 决策', tone: 'warning' }, + { id: 'L0', label: 'L0 · Raw conversation', short: 'Raw conversation', desc: 'Raw conversation / tool-call log, uncompressed', tone: 'default' }, + { id: 'L1', label: 'L1 · Atomic memory', short: 'Atomic memory', desc: 'Minimal facts / constraints extracted from the raw text', tone: 'brand' }, + { id: 'L2', label: 'L2 · Scene memory', short: 'Scene memory', desc: 'Summary aggregating multiple atomic memories around a scene', tone: 'success' }, + { id: 'L3', label: 'L3 · Core memory', short: 'Core memory', desc: 'Distilled core principles / templates / decisions', tone: 'warning' }, ]; export const SCOPE_TAB_LABELS: Record = { - all: '全部', - team: '团队资产', - fixed: 'Agent 资产', - scope: '可分配资产', - personal: '我的资产分配', + all: 'All', + team: 'Team assets', + fixed: 'Agent assets', + scope: 'Allocatable assets', + personal: 'My asset allocations', }; diff --git a/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/utils.ts b/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/utils.ts index 07d9ef5..8191a89 100644 --- a/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/utils.ts +++ b/MemoryPanel/web/src/pages/memory/ChatMemoryPage/components/utils.ts @@ -27,7 +27,7 @@ export function formatShortTime(ms: number): string { yesterday.getFullYear() === d.getFullYear() && yesterday.getMonth() === d.getMonth() && yesterday.getDate() === d.getDate() - ) return '昨天'; + ) return 'Yesterday'; return `${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; } diff --git a/MemoryPanel/web/src/pages/skills/SkillsPage/components/ForkSkillDialog.tsx b/MemoryPanel/web/src/pages/skills/SkillsPage/components/ForkSkillDialog.tsx index 909443b..2027015 100644 --- a/MemoryPanel/web/src/pages/skills/SkillsPage/components/ForkSkillDialog.tsx +++ b/MemoryPanel/web/src/pages/skills/SkillsPage/components/ForkSkillDialog.tsx @@ -64,7 +64,7 @@ export default function ForkSkillDialog(props: { async function submit(): Promise { if (!agentId) { - setError('请选择 agent。'); + setError('Select an agent.'); return; } setError(null); @@ -86,7 +86,7 @@ export default function ForkSkillDialog(props: { }); if (existing.items.some((s) => s.name === newName)) { throw new Error( - `Agent "${agentId}" 下已存在同名 skill "${newName}"(单个 agent 不允许重名)。请先删除旧副本再重试。` + `Agent "${agentId}" already has a skill named "${newName}" (an agent can't have duplicate names). Delete the old copy first, then retry.` ); } @@ -127,11 +127,11 @@ export default function ForkSkillDialog(props: { }); const resourceInfo = resources.length > 0 - ? `(已复制 ${resources.length} 个资源文件)` + ? ` (copied ${resources.length} resource files)` : (full.manifest?.length ?? 0) > 0 - ? `(注意:原 skill 有 ${full.manifest?.length} 个资源文件,复制均失败,如需请手动重新 import)` + ? ` (note: the original skill has ${full.manifest?.length} resource files, all failed to copy — re-import manually if needed)` : ''; - setSuccess(`已 fork "${props.skillName}" @ ${agentId}${resourceInfo}`); + setSuccess(`Forked "${props.skillName}" @ ${agentId}${resourceInfo}`); setTimeout(() => props.onForked(created), 800); } catch (err) { setError(err instanceof Error ? err.message : String(err)); @@ -141,27 +141,27 @@ export default function ForkSkillDialog(props: { } return ( - + - - 将 {props.skillName} 复制一份给所选 agent。副本与源 skill 解耦,agent 之后可独立修改副本。可在下方自定义副本名。 + + Copy {props.skillName} to the selected agent. The copy is decoupled from the source skill, and the agent can edit it independently afterward. You can customize the copy's name below. {error && {error}} @@ -170,7 +170,7 @@ export default function ForkSkillDialog(props: { - + ); diff --git a/MemoryPanel/web/src/pages/skills/SkillsPage/components/ImportSkillDialog.tsx b/MemoryPanel/web/src/pages/skills/SkillsPage/components/ImportSkillDialog.tsx index 1ddd848..317b844 100644 --- a/MemoryPanel/web/src/pages/skills/SkillsPage/components/ImportSkillDialog.tsx +++ b/MemoryPanel/web/src/pages/skills/SkillsPage/components/ImportSkillDialog.tsx @@ -101,7 +101,7 @@ function partitionFiles(files: File[]): { skillName: null, mainFile: null, resources: [], - warning: '目录中找不到 SKILL.md。请确保至少有一个 SKILL.md 在根目录或 / 下。' + warning: 'No SKILL.md found in the directory. Make sure there is at least one SKILL.md at the root or under /.' }; } const mainSegments = mainRelPath.split('/'); @@ -159,30 +159,30 @@ export default function ImportSkillDialog(props: { try { const agentId = props.target === 'fixed' ? (selectedAgentId || props.agentId || '') : ''; if (props.target === 'fixed' && !agentId) { - throw new Error('请选择归属 Agent。'); + throw new Error('Select an owning agent.'); } - if (!props.teamId) throw new Error('缺少 team 上下文,无法导入。'); + if (!props.teamId) throw new Error('Missing team context — cannot import.'); // ==== 对话导入:直接调 skill/extract ==== if (mode === 'session') { const raw = sessionPayload.trim(); - if (!raw) throw new Error('请粘贴对话 JSON。'); + if (!raw) throw new Error('Paste the conversation JSON.'); let parsed: Record; try { parsed = JSON.parse(raw); } catch (e) { - throw new Error(`对话 JSON 解析失败:${e instanceof Error ? e.message : String(e)}`); + throw new Error(`Failed to parse conversation JSON: ${e instanceof Error ? e.message : String(e)}`); } if (!Array.isArray((parsed as { messages?: unknown }).messages)) { - throw new Error('对话 JSON 缺少 messages 数组字段。'); + throw new Error('The conversation JSON is missing the messages array field.'); } const msgs = (parsed as { messages: unknown[] }).messages; if (msgs.length === 0) { - throw new Error('对话 messages 不能为空。'); + throw new Error('Conversation messages cannot be empty.'); } // 后端 extract 接口限制 messages 最多 500 条(见 iWiki §3.13), if (msgs.length > 500) { - throw new Error(`对话消息过多(${msgs.length} 条),接口最多支持 500 条,请删减后重试。`); + throw new Error(`Too many conversation messages (${msgs.length}); the API supports up to 500 — trim and try again.`); } // 组装 extract 入参。身份字段(user_id/team_id/agent_id)强制用当前 UI @@ -238,18 +238,18 @@ export default function ImportSkillDialog(props: { if (raced === 'timeout') { softTimedOut = true; setResult( - '提交成功,提取任务已受理。预计 1-3 分钟后完成,请稍后刷新 skill 列表查看结果。', + 'Submitted successfully — the extraction task has been accepted. It should finish in 1-3 minutes; refresh the skill list later to see the result.', ); } else if (raced) { setResult( - '提交成功,提取任务已受理。预计 1-3 分钟后完成,请稍后刷新 skill 列表查看结果。' - + `\n任务 ID:${raced.task_id}`, + 'Submitted successfully — the extraction task has been accepted. It should finish in 1-3 minutes; refresh the skill list later to see the result.' + + `\nTask ID: ${raced.task_id}`, ); } else { // extractPromise 被软超时后 catch 成 null 的分支(正常不会走到这里, // 因为软超时已经先设过 result 了),兜底保持一致文案。 setResult( - '提交成功,提取任务已受理。预计 1-3 分钟后完成,请稍后刷新 skill 列表查看结果。', + 'Submitted successfully — the extraction task has been accepted. It should finish in 1-3 minutes; refresh the skill list later to see the result.', ); } setTimeout(() => props.onImported(), 1500); @@ -258,14 +258,14 @@ export default function ImportSkillDialog(props: { // ==== 目录导入:走 v3 create + files/write ==== if (!partition?.mainFile) { - throw new Error(partition?.warning ?? '请选择包含 SKILL.md 的目录。'); + throw new Error(partition?.warning ?? 'Select a directory that contains SKILL.md.'); } const content = await readAsUtf8(partition.mainFile); const fmMatch = content.match(/^---\n([\s\S]*?)\n---/); const nameMatch = fmMatch?.[1].match(/^name:\s*(.+)$/m); const name = nameMatch?.[1].trim().replace(/^["']|["']$/g, '') || partition.skillName || ''; if (!name) { - throw new Error('无法从 SKILL.md 或目录结构推断 skill 名称,请检查 frontmatter 或目录布局。'); + throw new Error('Could not infer a skill name from SKILL.md or the directory structure — check the frontmatter or directory layout.'); } const resourceFiles: { path: string; file: File; isBinary: boolean }[] = partition.resources.map( ({ path, file }) => ({ path, file, isBinary: !looksLikeText(file) }), @@ -297,7 +297,7 @@ export default function ImportSkillDialog(props: { resources: resources.length ? resources : undefined, }); - setResult(`导入成功:${name}(${resourceFiles.length} 个资源文件)`); + setResult(`Imported successfully: ${name} (${resourceFiles.length} resource files)`); setTimeout(() => props.onImported(), 800); } catch (err) { setError(err instanceof Error ? err.message : String(err)); @@ -312,27 +312,27 @@ export default function ImportSkillDialog(props: { const showAgentPicker = props.target === 'fixed' && !!props.agents; return ( - + - 上传 SKILL.md 目录,或粘贴一段对话让系统自动提炼出 skill。 + Upload a SKILL.md directory, or paste a conversation to let the system automatically distill a skill. {/* 归属 Agent —— 必选项。始终展示在导入方式上方, 与 ChatMemoryPanel.ImportBlockDialog 一致:即便外层已经传了 agentId 也允许在弹窗里重选。 */} {showAgentPicker && ( {props.agents!.length === 0 ? ( - 当前 team 暂无 agent,无法导入。请先到团队管理中创建至少一个 agent。 + This team has no agents yet, so import isn't possible. Create at least one agent in team management first. ) : ( - + - + - +
{assets.loading ? ( -
加载团队资产中…
+
Loading team assets…
) : ( <>
- 原子能力 - 只读 · 资源绑定请在创建或者对应资源管理页面修改设置 + Atomic capabilities + Read-only · to change resource bindings, edit them on creation or in the corresponding resource management page
} - title="Wiki 知识库" + title="Wiki knowledge base" selectedCount={llmWikis.length} totalCount={boundWikis.length} open={wikiOpen} @@ -286,7 +286,7 @@ export default function AgentEditDialog({ } - title="Skill 技能" + title="Skill" selectedCount={skills.length} totalCount={boundSkills.length} open={skillsOpen} @@ -323,9 +323,9 @@ export default function AgentEditDialog({
- + diff --git a/MemoryPanel/web/src/pages/team/components/AgentGrid.tsx b/MemoryPanel/web/src/pages/team/components/AgentGrid.tsx index 61b99be..6880827 100644 --- a/MemoryPanel/web/src/pages/team/components/AgentGrid.tsx +++ b/MemoryPanel/web/src/pages/team/components/AgentGrid.tsx @@ -95,7 +95,7 @@ export default function AgentGrid({ className={`_memory-agents-name-trigger${editable ? ' _memory-agents-name-trigger--editable' : ''}`} onClick={() => editable && onEditAgent(agent)} disabled={!editable} - title={editable ? '点击查看并编辑该 Agent' : `仅 owner(${agent.owner_user_id || '未设置'})或 team 管理员可编辑`} + title={editable ? 'Click to view and edit this agent' : `Only the owner (${agent.owner_user_id || 'not set'}) or a team admin can edit`} > {agent.icon} {agent.name} @@ -108,7 +108,7 @@ export default function AgentGrid({ const ownerIsMe = agent.owner_user_id === currentUser; return ( - {agent.owner_user_id || '未设置'}{ownerIsMe && '(你)'} + {agent.owner_user_id || 'not set'}{ownerIsMe && ' (you)'} ); } @@ -131,9 +131,9 @@ export default function AgentGrid({

Agents

- 当前 team「{activeTeam.name}」 - ({activeTeam.team_id}) - 中由你创建的 Agent · {agentsLoading ? '加载中…' : `共 ${agents.length} 个`} + Agents you created in team "{activeTeam.name}" + ({activeTeam.team_id}) + · {agentsLoading ? 'Loading…' : `${agents.length} total`}
@@ -145,9 +145,9 @@ export default function AgentGrid({ type="primary" onClick={onCreateAgent} style={{ visibility: isAdmin ? 'hidden' : 'visible' }} - title="在当前 team 下创建一个新 Agent" + title="Create a new agent in the current team" > - 新建 Agent + Create agent } right={ @@ -155,7 +155,7 @@ export default function AgentGrid({ {canSeeAllAgents && ( -
一句话描述
+
One-line description
-
角色定位 prompt
+
Role prompt
-
规则固定 prompt
+
Rules prompt
- +
)} - +
- agent_id 由后端生成并保证全局唯一(不限于本 team)。 + agent_id is generated by the backend and guaranteed globally unique (not limited to this team).
- + {assets.loading ? ( -
加载团队资产中…
+
Loading team assets…
) : ( <>
- 原子能力: + Atomic capabilities: {totalSelected > 0 && ( )}
} - title="Wiki 知识库" + title="Wiki knowledge base" selectedCount={llmWikis.length} totalCount={assets.wikis.length} open={wikiOpen} @@ -360,7 +360,7 @@ export default function CreateAgentDialog({ } - title="Skill 技能" + title="Skill" selectedCount={skills.length} totalCount={assets.skills.length} open={skillsOpen} @@ -406,9 +406,9 @@ export default function CreateAgentDialog({ chatMemories, })} > - 创建 + Create - + ); diff --git a/MemoryPanel/web/src/pages/team/components/CreateTeamDialog.tsx b/MemoryPanel/web/src/pages/team/components/CreateTeamDialog.tsx index 0bdcacb..58c87cc 100644 --- a/MemoryPanel/web/src/pages/team/components/CreateTeamDialog.tsx +++ b/MemoryPanel/web/src/pages/team/components/CreateTeamDialog.tsx @@ -18,32 +18,32 @@ export default function CreateTeamDialog({ const [description, setDescription] = useState(''); const canSubmit = name.trim().length > 0 && !busy; return ( - + - + - + - - + + ); diff --git a/MemoryPanel/web/src/pages/team/components/MemberSection.tsx b/MemoryPanel/web/src/pages/team/components/MemberSection.tsx index 29b7205..807d16d 100644 --- a/MemoryPanel/web/src/pages/team/components/MemberSection.tsx +++ b/MemoryPanel/web/src/pages/team/components/MemberSection.tsx @@ -30,9 +30,9 @@ export function MemberSection({ async function handleRemove(userId: string) { const ok = await tea.confirm({ - message: `移除成员 ${userId}?`, - description: '此操作仅将该用户移出当前团队,不会删除用户账号。', - okText: '移除', + message: `Remove member ${userId}?`, + description: 'This only removes the user from the current team; it does not delete the user account.', + okText: 'Remove', }); if (!ok) return; setRemoving(userId); @@ -51,17 +51,17 @@ export function MemberSection({
-
成员({team.members.length})
+
Members ({team.members.length})
{team.team_id}
- 「{team.name}」的人类成员;admin 可管理 team 资产,member 可使用资产并创建 task · - 点击卡片查看详情 + Human members of "{team.name}"; admins can manage team assets, members can use assets + and create tasks · click a card for details
{canAddMember && ( - )}
@@ -118,7 +118,7 @@ function MemberCard({
{displayName} - {isMe && (你)} + {isMe && (you)}
{hasUsername && (
@@ -127,7 +127,7 @@ function MemberCard({ )}
{role} - {isOwner ? ' · 创建者' : ''} + {isOwner ? ' · Creator' : ''}
@@ -137,8 +137,8 @@ function MemberCard({ onClick={(e) => { e.stopPropagation(); onRemove(); }} disabled={removing} className="_memory-member-remove-btn" - title="移除该成员" - aria-label="移除该成员" + title="Remove this member" + aria-label="Remove this member" > {removing ? '…' : } @@ -188,11 +188,11 @@ export function AddMemberDialog({ async function submitExisting() { const id = userId.trim(); if (!id) { - setError('请输入对方的 user_id。'); + setError('Enter the user_id of the person to add.'); return; } if (id === currentUser) { - setError('不能添加自己;如需调整角色,请由其他 team admin 操作。'); + setError('You cannot add yourself; ask another team admin to adjust roles.'); return; } setSubmitting(true); @@ -211,12 +211,12 @@ export function AddMemberDialog({ async function submitNew() { const username = newUsername.trim(); if (!username) { - setError('请输入用户名。'); + setError('Enter a username.'); return; } // 用户名只允许英文字母、数字、下划线(与后端 user_id 段校验规则一致) if (!/^[A-Za-z0-9_]+$/.test(username)) { - setError('用户名仅支持英文字母、数字、下划线,不能包含其他符号或空格。'); + setError('Usernames may only contain letters, numbers, and underscores — no other symbols or spaces.'); return; } setSubmitting(true); @@ -260,15 +260,15 @@ export function AddMemberDialog({ return ( 添加成员到「{team.name}」{team.team_id}} + caption={<>Add member to "{team.name}"{team.team_id}} size="m" onClose={onClose} disableEscape={submitting} > - {!canGrantAdmin && 仅 team admin 可授予 admin 角色} + {!canGrantAdmin && Only team admins can grant the admin role}
- + {canCreateUser ? ( ) : (
- 添加已有用户(按 user_id 邀请加入团队)。新建用户账号须全局 admin 权限。 + Add an existing user (invite by user_id to join the team). Creating a new user account requires global admin permission.
)}
@@ -301,14 +301,14 @@ export function AddMemberDialog({ setError(null); }} onPressEnter={() => void handleSubmit()} - placeholder="例如 usr-xxxxxxxxxxxx" + placeholder="e.g. usr-xxxxxxxxxxxx" /> -
可让对方在「我的资料」里复制发给你
+
They can copy this from "My profile" and send it to you
) : ( <> - +
void handleSubmit()} - placeholder="例如 alice" + placeholder="e.g. alice" /> {newUsername.trim() && !/^[A-Za-z0-9_]+$/.test(newUsername.trim()) ? (
- 仅支持英文字母、数字、下划线,不能包含空格或其他符号 + Only letters, numbers, and underscores are allowed — no spaces or other symbols
) : ( -
英文字母、数字、下划线,创建后不可修改
+
Letters, numbers, underscores; cannot be changed after creation
)}
)} - + handleSearch(e.target.value)} /> @@ -244,11 +244,11 @@ export default function KnowledgeGraph({ data, loading, onNodeClick, highlightNo )}
- - + +
+ onClick={() => setHideStructural(!hideStructural)} title="Hide structural nodes">Hide structural {filteredData.nodes.length} nodes · {filteredData.edges.length} edges diff --git a/MemoryPanel/web/src/pages/wiki/WikiPage/components/WikiSourcesPanel.tsx b/MemoryPanel/web/src/pages/wiki/WikiPage/components/WikiSourcesPanel.tsx index 3f8e02a..f0d6c80 100644 --- a/MemoryPanel/web/src/pages/wiki/WikiPage/components/WikiSourcesPanel.tsx +++ b/MemoryPanel/web/src/pages/wiki/WikiPage/components/WikiSourcesPanel.tsx @@ -168,12 +168,12 @@ const WIKI_STATUS_BADGE: Record< WikiDetail['status'], { label: string; theme: 'warning' | 'success' | 'error' | 'default' } > = { - draft: { label: '待加工', theme: 'warning' }, - pending: { label: '排队中', theme: 'warning' }, - processing: { label: '加工中', theme: 'warning' }, - ready: { label: '就绪', theme: 'success' }, - failed: { label: '失败', theme: 'error' }, - missing: { label: '已丢失', theme: 'error' }, + draft: { label: 'Awaiting processing', theme: 'warning' }, + pending: { label: 'Queued', theme: 'warning' }, + processing: { label: 'Processing', theme: 'warning' }, + ready: { label: 'Ready', theme: 'success' }, + failed: { label: 'Failed', theme: 'error' }, + missing: { label: 'Lost', theme: 'error' }, }; function WikiStatusBadge({ status }: { status: WikiDetail['status'] }) { const b = WIKI_STATUS_BADGE[status] ?? { label: status, theme: 'default' as const }; @@ -226,10 +226,10 @@ const TYPE_COLOR_FALLBACK = 'var(--tea-color-text-tertiary)'; type WikiScopeTab = 'all' | 'team' | 'fixed' | 'scope'; const SCOPE_LABELS: Record = { - all: '全部', - team: '团队 Wiki 池', - fixed: 'Agent 资产', - scope: '可配置范围', + all: 'All', + team: 'Team wiki pool', + fixed: 'Agent assets', + scope: 'Configurable scope', }; /** @@ -243,7 +243,7 @@ function WikiOwnerLabel({ userId, currentUserId }: { userId: string; currentUser return ( @{name || userId} - {userId === currentUserId && (你)} + {userId === currentUserId && (you)} ); } @@ -306,7 +306,7 @@ export default function WikiSourcesPanel() { const items = await knowledgeApi.wiki.agentFixed(agentFilter); setFixedBoundIds(new Set(items.map((it) => it.knowledge_id))); } catch (e: any) { - tea.notify.error(e?.message || '加载固定资产失败'); + tea.notify.error(e?.message || 'Failed to load fixed assets'); setFixedBoundIds(new Set()); } }, [agentFilter]); @@ -480,7 +480,7 @@ export default function WikiSourcesPanel() { } finally { setGraphLoading(false); } - if (hadError) tea.notify.error('加载 Wiki 详情失败,部分内容可能不完整'); + if (hadError) tea.notify.error('Failed to load wiki details. Some content may be incomplete.'); }, []); const runningWikiKey = useMemo( @@ -529,19 +529,19 @@ export default function WikiSourcesPanel() { async function handleUnbindWiki(wikiId: string) { if (!agentFilter) return; const ok = await tea.confirm({ - message: '确认解绑该 Wiki?', - description: '将从当前 agent 移除该 Wiki 绑定。', - okText: '解绑', + message: 'Unbind this wiki?', + description: 'This removes the wiki binding from the current agent.', + okText: 'Unbind', }); if (!ok) return; try { await knowledgeApi.wiki.unbind(wikiId, agentFilter); - tea.notify.success('已解绑'); + tea.notify.success('Unbound'); if (selectedWikiId === wikiId) setSelectedWikiId(''); await fetchFixedBindings(); await fetchSources(); } catch (e: any) { - tea.notify.error(e?.message || '解绑失败'); + tea.notify.error(e?.message || 'Unbind failed'); } } @@ -551,7 +551,7 @@ export default function WikiSourcesPanel() { setSubmitting(true); try { await knowledgeApi.wiki.create(activeTeamId, newName.trim()); - tea.notify.success(`Wiki「${newName.trim()}」已创建`); + tea.notify.success(`Wiki "${newName.trim()}" created`); setShowCreate(false); setNewName(''); fetchSources(); @@ -566,7 +566,7 @@ export default function WikiSourcesPanel() { // 防御:同一时间只允许一个 Wiki 提取,避免并发 ingest 导致后端排队混乱。 // 按钮已按 ingestBusy 禁用,这里再挡一层防止绕过。 if (ingestBusy) { - tea.notify.warning('已有 Wiki 正在提取,请等待当前任务完成后再试。'); + tea.notify.warning('A wiki is already being extracted. Wait for the current task to finish and try again.'); return; } const wiki = sources.find((s) => s.wiki_id === wikiId); @@ -576,7 +576,7 @@ export default function WikiSourcesPanel() { wikiId, wiki: name, currentFile: '', - detail: '正在触发抽取...', + detail: 'Triggering extraction...', done: 0, total: 100, checkCount: 0, @@ -592,14 +592,14 @@ export default function WikiSourcesPanel() { const checkedAt = new Date(ev.ts).toLocaleTimeString(); if (ev.type === 'file_start') { next.currentFile = ev.file || ''; - next.detail = ev.detail || '处理中...'; + next.detail = ev.detail || 'Processing...'; next.done = ev.done ?? prev.done; next.total = ev.total ?? prev.total; next.lastCheckedAt = checkedAt; } else if (ev.type === 'file_done') { next.done = ev.done ?? prev.done; next.total = ev.total ?? prev.total; - next.detail = ev.detail || `已检查 ${next.done}/${next.total}`; + next.detail = ev.detail || `Checked ${next.done}/${next.total}`; next.checkCount = prev.checkCount + 1; next.lastCheckedAt = checkedAt; if (ev.file) next.log = [...prev.log, { file: ev.file, status: 'done' }]; @@ -612,7 +612,7 @@ export default function WikiSourcesPanel() { } else if (ev.type === 'batch_done') { next.done = ev.done ?? 100; next.total = ev.total ?? 100; - next.detail = ev.detail || '抽取完成'; + next.detail = ev.detail || 'Extraction complete'; next.lastCheckedAt = checkedAt; } return next; @@ -624,30 +624,30 @@ export default function WikiSourcesPanel() { active: false, done: 100, total: 100, - detail: `完成!当前 ${result.ingested} 页`, + detail: `Done! ${result.ingested} pages so far`, currentFile: '', })); - tea.notify.success(`Wiki 抽取完成,共 ${result.ingested} 页`); + tea.notify.success(`Wiki extraction complete — ${result.ingested} pages total`); fetchSources(); fetchDetail(wikiId); }, onError: (err) => { - setIngestState((prev) => ({ ...prev, active: false, detail: `错误: ${err}` })); - tea.notify.error(err || 'Wiki 抽取失败'); + setIngestState((prev) => ({ ...prev, active: false, detail: `Error: ${err}` })); + tea.notify.error(err || 'Wiki extraction failed'); }, }, activeTeamId ?? '', ); setIngestState((prev) => prev.active - ? { ...prev, active: false, detail: prev.log.length > 0 ? '完成' : prev.detail } + ? { ...prev, active: false, detail: prev.log.length > 0 ? 'Done' : prev.detail } : prev, ); fetchSources(); }; const handleDelete = async (wikiId: string, name: string) => { - const ok = await tea.confirm({ message: `确定要删除 Wiki「${name}」吗?`, okText: '删除' }); + const ok = await tea.confirm({ message: `Delete wiki "${name}"?`, okText: 'Delete' }); if (!ok) return; try { await knowledgeApi.wiki.delete(wikiId); @@ -689,7 +689,7 @@ export default function WikiSourcesPanel() { setReadContent(r?.content || ''); } catch (e: any) { setReadContent(''); - tea.notify.error(e?.message || '读取页面内容失败'); + tea.notify.error(e?.message || 'Failed to read page content'); } finally { setReadLoading(false); } @@ -699,42 +699,42 @@ export default function WikiSourcesPanel() { if (!selectedWikiId) return; const ref = (page as any).id || page.path; const ok = await tea.confirm({ - message: `确认删除页面「${page.title || ref}」?`, - description: '会删除该 wiki 页面并清理引用。', - okText: '删除', + message: `Delete page "${page.title || ref}"?`, + description: 'This deletes the wiki page and cleans up references.', + okText: 'Delete', }); if (!ok) return; try { await knowledgeApi.wiki.pageDelete(selectedWikiId, [ref]); - tea.notify.success('已删除页面'); + tea.notify.success('Page deleted'); if (selectedPage && ((selectedPage as any).id || selectedPage.path) === ref) { setSelectedPage(null); setReadContent(''); } await fetchDetail(selectedWikiId); } catch (e: any) { - tea.notify.error(e?.message || '删除页面失败'); + tea.notify.error(e?.message || 'Failed to delete page'); } }; const handleDeleteRaw = async (filename: string) => { if (!selectedWikiId) return; const ok = await tea.confirm({ - message: `确认删除原始文档「${filename}」?`, - description: '会删除原始文档,并同步清理由它派生的页面。', - okText: '删除', + message: `Delete original document "${filename}"?`, + description: 'This deletes the original document and cleans up pages derived from it.', + okText: 'Delete', }); if (!ok) return; try { await knowledgeApi.wiki.rawDelete(selectedWikiId, [filename]); - tea.notify.success('已删除原始文档'); + tea.notify.success('Original document deleted'); if (selectedPage?.path === `raw/${filename}`) { setSelectedPage(null); setReadContent(''); } await fetchDetail(selectedWikiId); } catch (e: any) { - tea.notify.error(e?.message || '删除原始文档失败'); + tea.notify.error(e?.message || 'Failed to delete original document'); } }; @@ -761,13 +761,13 @@ export default function WikiSourcesPanel() { if (existing.length === 0) return true; return tea.confirm({ - message: `检测到 ${existing.length} 个同名文件`, - description: `继续上传将覆盖原有内容:${formatOverwriteFilenames(existing)}`, - okText: '覆盖并上传', - cancelText: '取消', + message: `Found ${existing.length} file(s) with the same name`, + description: `Continuing will overwrite existing content: ${formatOverwriteFilenames(existing)}`, + okText: 'Overwrite and upload', + cancelText: 'Cancel', }); } catch (e: unknown) { - tea.notify.error(e instanceof Error ? e : '获取已有文档失败,已取消上传'); + tea.notify.error(e instanceof Error ? e : 'Failed to fetch existing documents. Upload canceled.'); return false; } }; @@ -778,15 +778,15 @@ export default function WikiSourcesPanel() { */ const offerIngestAfterUpload = async (wikiId: string, uploadedCount: number) => { const shouldIngest = await tea.confirm({ - message: `${uploadedCount} 个文档已上传`, - description: '文档尚未抽取为可检索页面。现在开始抽取后,才能在页面、图谱和搜索中使用这些内容。', - okText: '开始抽取', - cancelText: '稍后处理', + message: `${uploadedCount} document(s) uploaded`, + description: "Documents haven't been extracted into searchable pages yet. Start extraction to use this content in pages, the graph, and search.", + okText: 'Start extraction', + cancelText: 'Later', }); if (shouldIngest) { void handleIngest(wikiId); } else { - tea.notify.info('文档已保存。需要时可点击 Wiki 详情页右上角的“开始抽取”。'); + tea.notify.info('Documents saved. Click "Start extraction" in the top-right of the wiki detail page when you\'re ready.'); } }; @@ -814,7 +814,7 @@ export default function WikiSourcesPanel() { uploadInFlightRef.current = false; setSubmitting(false); if (failures.length === 0) { - tea.notify.success(`已上传 ${valid.length} 个文档`); + tea.notify.success(`${valid.length} document(s) uploaded`); setMdDocs([{ filename: '', content: '' }]); setShowAddDoc(false); fetchDetail(selectedWikiId); @@ -827,8 +827,8 @@ export default function WikiSourcesPanel() { .slice(0, 3) .map((f) => `${f.filename}: ${f.error}`) .join('\n'); - const more = failures.length > 3 ? `\n…及其它 ${failures.length - 3} 个` : ''; - tea.notify.error(`${okCount} 个成功,${failures.length} 个失败:\n${shown}${more}`); + const more = failures.length > 3 ? `\n...and ${failures.length - 3} more` : ''; + tea.notify.error(`${okCount} succeeded, ${failures.length} failed:\n${shown}${more}`); fetchDetail(selectedWikiId); setRawRefreshKey((k) => k + 1); if (okCount > 0) await offerIngestAfterUpload(selectedWikiId, okCount); @@ -868,7 +868,7 @@ export default function WikiSourcesPanel() { const failed = results.filter((r) => r.status === 'rejected').length; const succeeded = results.length - failed; if (failed === 0) { - tea.notify.success(`已上传 ${succeeded} 个文件`); + tea.notify.success(`${succeeded} file(s) uploaded`); setPendingFiles([]); setUploadProgress({}); setShowAddDoc(false); @@ -881,7 +881,7 @@ export default function WikiSourcesPanel() { if (r.status === 'rejected') setUploadProgress((prev) => ({ ...prev, [pendingFiles[i].name]: 'error' })); }); - tea.notify.error(`${succeeded} 个成功,${failed} 个失败`); + tea.notify.error(`${succeeded} succeeded, ${failed} failed`); fetchDetail(selectedWikiId); setRawRefreshKey((k) => k + 1); if (succeeded > 0) await offerIngestAfterUpload(selectedWikiId, succeeded); @@ -925,13 +925,13 @@ export default function WikiSourcesPanel() { if (hasManualIngestState || !runningWiki) return ingestState; const stage = wikiStageLabel(runningWiki.status, runningWiki.internal_status); const pageHint = - typeof runningWiki.page_count === 'number' ? `,当前 ${runningWiki.page_count} 页` : ''; + typeof runningWiki.page_count === 'number' ? `, ${runningWiki.page_count} pages so far` : ''; return { active: true, wikiId: runningWiki.wiki_id ?? '', wiki: runningWiki.name, currentFile: '', - detail: `状态恢复:${stage}${pageHint}`, + detail: `Status restored: ${stage}${pageHint}`, done: wikiProgressPercent(runningWiki.status, runningWiki.internal_status), total: 100, checkCount: 0, @@ -999,7 +999,7 @@ export default function WikiSourcesPanel() {
/ {wikiName} @@ -1009,7 +1009,7 @@ export default function WikiSourcesPanel() { {wikiName} {source && } - {pages.length} 页 + {pages.length} pages
)}
@@ -1078,9 +1078,9 @@ export default function WikiSourcesPanel() { {displayIngestState.checkCount > 0 && ( - 已实际查询 {displayIngestState.checkCount} 次 + Checked {displayIngestState.checkCount} times {displayIngestState.lastCheckedAt - ? `,最近 ${displayIngestState.lastCheckedAt}` + ? `, last at ${displayIngestState.lastCheckedAt}` : ''} )} @@ -1124,7 +1124,7 @@ export default function WikiSourcesPanel() { label: ( - 概览 + Overview ), }, @@ -1133,7 +1133,7 @@ export default function WikiSourcesPanel() { label: ( - 图谱 + Graph ), }, @@ -1142,7 +1142,7 @@ export default function WikiSourcesPanel() { label: ( - 页面 + Pages ), }, @@ -1151,7 +1151,7 @@ export default function WikiSourcesPanel() { label: ( - 搜索 + Search ), }, @@ -1160,14 +1160,14 @@ export default function WikiSourcesPanel() {
- - - + + +
- + {types.length === 0 ? ( - + ) : (
{types.map((type) => { @@ -1192,7 +1192,7 @@ export default function WikiSourcesPanel() { /> - {count}({pct}%) + {count} ({pct}%)
); @@ -1202,9 +1202,9 @@ export default function WikiSourcesPanel() {
- + {pages.length === 0 ? ( - + ) : (
{pages.slice(0, 9).map((page) => ( @@ -1278,7 +1278,7 @@ export default function WikiSourcesPanel() { .then((result: any) => setReadContent(result?.items?.[0]?.content || '')) .catch((error: any) => { setReadContent(''); - tea.notify.error(error?.message || '读取原始文档失败'); + tea.notify.error(error?.message || 'Failed to read original document'); }) .finally(() => setReadLoading(false)); }} @@ -1290,12 +1290,12 @@ export default function WikiSourcesPanel() { value={searchQuery} onChange={setSearchQuery} onSearch={handleSearch} - placeholder="搜索文档内容…" + placeholder="Search document content..." /> {searching && } {!searching && searchResults.length > 0 && ( <> - {searchResults.length} 条结果 + {searchResults.length} results
{searchResults.map((result, index) => (
@@ -1346,16 +1346,16 @@ export default function WikiSourcesPanel() { {showAddDoc && ( setShowAddDoc(false)} disableEscape={submitting} > - 选择方式导入文档 + Choose a way to import documents 0) { tea.notify.warning( - `已忽略 ${rejected} 个非 Markdown 文件(仅支持 .md/.txt/.markdown)`, + `Ignored ${rejected} non-Markdown file(s) (.md/.txt/.markdown only)`, ); } if (allowed.length > 0) setPendingFiles((prev) => [...prev, ...allowed]); }} > - 拖拽或点击选择 Markdown 文件(可多选) + Drag and drop or click to select Markdown files (multiple allowed)
{pendingFiles.length > 0 && (
@@ -1410,7 +1410,7 @@ export default function WikiSourcesPanel() { setPendingFiles((prev) => prev.filter((_, j) => j !== i)) } > - 删除 + Delete )}
@@ -1419,14 +1419,14 @@ export default function WikiSourcesPanel() { )} {pendingFiles.length > 0 && (
- {pendingFiles.length} 个文件待上传 + {pendingFiles.length} file(s) pending upload
)} @@ -1453,7 +1453,7 @@ export default function WikiSourcesPanel() { type="text" onClick={() => setMdDocs((prev) => prev.filter((_, j) => j !== i))} > - 删除 + Delete )}
@@ -1466,19 +1466,19 @@ export default function WikiSourcesPanel() { prev.map((d, j) => (j === i ? { ...d, content: v } : d)), ) } - placeholder="# 标题" + placeholder="# Title" /> ))}
{mdDocs.filter((d) => d.filename.trim() && d.content.trim()).length}{' '} - 个待上传 + pending upload
@@ -1508,7 +1508,7 @@ export default function WikiSourcesPanel() { const rejected = all.length - allowed.length; if (rejected > 0) { tea.notify.warning( - `已忽略 ${rejected} 个非 Markdown 文件(仅支持 .md/.txt/.markdown)`, + `Ignored ${rejected} non-Markdown file(s) (.md/.txt/.markdown only)`, ); } if (allowed.length > 0) setPendingFiles((prev) => [...prev, ...allowed]); @@ -1528,12 +1528,12 @@ export default function WikiSourcesPanel() { return (
{activeTeam - ? `${activeTeam.name} · 共 ${stats.total} 个知识库` - : `共 ${stats.total} 个知识库`} + ? `${activeTeam.name} · ${stats.total} knowledge bases total` + : `${stats.total} knowledge bases total`} } scope={ @@ -1554,10 +1554,10 @@ export default function WikiSourcesPanel() { value={agentFilter} onChange={setAgentFilter} disabled={teamAgents.length === 0} - placeholder="无可选 Agent" + placeholder="No agent available" options={teamAgents.map((agent) => ({ value: agent.id, - text: `${agent.name}(${agent.id})`, + text: `${agent.name} (${agent.id})`, }))} /> ) : undefined @@ -1567,16 +1567,16 @@ export default function WikiSourcesPanel() {
- - - - + + + +
setShowCreate(true)}> - + 新建 Wiki + + New wiki } right={ @@ -1584,15 +1584,15 @@ export default function WikiSourcesPanel() { setStatusFilter(value as StatusFilter)} options={[ - { value: 'all', text: '全部状态' }, - { value: 'ready', text: '就绪' }, - { value: 'processing', text: '处理中' }, + { value: 'all', text: 'All statuses' }, + { value: 'ready', text: 'Ready' }, + { value: 'processing', text: 'Processing' }, ]} /> - 暂无 Wiki 知识库 - 点击上方“+ 新建 Wiki”创建第一个 + No wiki knowledge bases + Click "+ New wiki" above to create your first one
} /> ) : filteredSources.length === 0 ? ( - + ) : viewMode === 'card' ? (
{filteredSources.map((source) => ( @@ -1641,20 +1641,20 @@ export default function WikiSourcesPanel() {
- {source.page_count ?? 0} 页 · {formatShortTime(source.last_sync_at)} + {source.page_count ?? 0} pages · {formatShortTime(source.last_sync_at)}
{scopeTab === 'fixed' ? ( - `固定资产 · ${agentFilter || '未选择 Agent'}` + `Fixed asset · ${agentFilter || 'No agent selected'}` ) : source.owner_user_id ? ( ) : ( - '团队 Wiki 池' + 'Team wiki pool' )}
-
ID:{source.wiki_id}
+
ID: {source.wiki_id}
( @@ -1802,9 +1802,9 @@ export default function WikiSourcesPanel() { team={activeTeam ? { team_id: activeTeam.team_id, name: activeTeam.name } : null} onClose={() => setAllocateTarget(null)} onAllocate={async (agentId) => { - if (!activeTeamId) throw new Error('请先选择 team'); + if (!activeTeamId) throw new Error('Select a team first'); await knowledgeApi.wiki.allocate(activeTeamId, allocateTarget.wiki_id, agentId); - tea.notify.success('已分配到 Agent'); + tea.notify.success('Assigned to agent'); await fetchSources(); if (scopeTab === 'fixed') await fetchFixedBindings(); }} @@ -1837,11 +1837,11 @@ function WikiActions({ return (
event.stopPropagation()}> {scopeTab === 'fixed' ? ( ) : ( )}
@@ -2010,7 +2010,7 @@ function PagesTabContent({ className={`_wiki-detail-filter-tag${pageTypeFilter === 'all' ? ' is-active' : ''}`} onClick={() => setPageTypeFilter('all')} > - 全部 {allPages.length} + All {allPages.length} {types.map((type) => (
); @@ -2087,7 +2087,7 @@ function PagesTabContent({ {tag.trim()} ))} - {metadata.created && 创建:{metadata.created}} + {metadata.created && Created: {metadata.created}} )} {readLoading ? ( @@ -2105,7 +2105,7 @@ function PagesTabContent({ ) : (
- 选择左侧页面查看内容 + Select a page on the left to view its content
)} @@ -2140,7 +2140,7 @@ function RawFilesSection({ knowledgeApi.wiki .rawList(wikiId) .then((r: any) => setFiles(r?.files || [])) - .catch((e: any) => tea.notify.error(e?.message || '加载原始文档列表失败')) + .catch((e: any) => tea.notify.error(e?.message || 'Failed to load original document list')) .finally(() => setLoading(false)); }, [wikiId]); @@ -2158,7 +2158,7 @@ function RawFilesSection({ if (loading) return (
- 原始文档加载中… + Loading original documents…
); if (files.length === 0) return null; @@ -2167,7 +2167,7 @@ function RawFilesSection({
@@ -2184,9 +2184,9 @@ function RawFilesSection({ type="text" className="_wiki-detail-page-delete" onClick={() => void handleDelete(file.filename)} - tooltip="删除原始文档" + tooltip="Delete original document" > - 删除 + Delete
))} @@ -2213,7 +2213,7 @@ function KnowledgeGraphEmbed({ highlightNode: string | null; }) { return ( - }> + }> +
- +
{props.team.name.slice(0, 1).toUpperCase()}
-
将创建到 team
+
Will be created in team
{props.team.name} {props.team.team_id} @@ -84,30 +84,30 @@ export default function TaskCreateDialog(props: {
- + - + {error && {error}} - - + + ); diff --git a/MemoryPanel/web/src/pages/workbench/WorkbenchPage/components/TaskWorkbench.tsx b/MemoryPanel/web/src/pages/workbench/WorkbenchPage/components/TaskWorkbench.tsx index e8bba40..312962b 100644 --- a/MemoryPanel/web/src/pages/workbench/WorkbenchPage/components/TaskWorkbench.tsx +++ b/MemoryPanel/web/src/pages/workbench/WorkbenchPage/components/TaskWorkbench.tsx @@ -44,8 +44,8 @@ function errMsg(e: unknown): string { // 历史的 待处理 / 阻塞 / 已归档 已下线(参见 backendStore.ts 里的 normalizeTaskStatus)。 const STATUS_LABEL: Record = { - running: '进行中', - completed: '已完成' + running: 'In progress', + completed: 'Completed' }; // Tag 组件合法 theme: default/primary/success/warning/error @@ -172,7 +172,7 @@ export default function TaskWorkbench(props: { // 谁点击「创建 Task」,谁就是 creator_user_id。 const team = teams.find((t) => t.team_id === draft.team_id); if (!team) { - tea.notify.error(`team「${draft.team_id}」不存在,无法创建 task。`); + tea.notify.error(`Team "${draft.team_id}" does not exist. Cannot create task.`); return; } try { @@ -207,15 +207,15 @@ export default function TaskWorkbench(props: { const team = teams.find((t) => t.team_id === task.team_id) ?? null; if (!canDeleteTask(task, team, currentUser) && !isAdmin) { tea.notify.warning( - `你不是 task「${task.title}」的创建者,也不是 team 管理员,无法删除。创建者: ${task.creator_user_id}` + `You are not the creator of task "${task.title}" nor a team admin, so you cannot delete it. Creator: ${task.creator_user_id}` ); return; } const ok = await tea.confirm({ - message: `确认删除 task「${task.title}」?`, + message: `Delete task "${task.title}"?`, description: `Task ID: ${task.task_id}`, - okText: '删除', - cancelText: '取消', + okText: 'Delete', + cancelText: 'Cancel', }); if (ok) { try { @@ -230,7 +230,7 @@ export default function TaskWorkbench(props: { // 权限:编辑 task(含切换 status)允许 team 内任意 member / admin const team = teams.find((t) => t.team_id === task.team_id) ?? null; if (!canEditTask(task, team, currentUser) && !isAdmin) { - tea.notify.warning('你不是该 team 的成员,无权修改此 task。'); + tea.notify.warning('You are not a member of this team and cannot modify this task.'); return; } try { @@ -242,7 +242,7 @@ export default function TaskWorkbench(props: { onUpdateTask={async (task, patch) => { const team = teams.find((t) => t.team_id === task.team_id) ?? null; if (!canEditTask(task, team, currentUser) && !isAdmin) { - tea.notify.warning('你不是该 team 的成员,无权修改此 task。'); + tea.notify.warning('You are not a member of this team and cannot modify this task.'); return; } try { @@ -278,9 +278,9 @@ function EmptyTeam() { return ( - 还没有可用的 Team + No team available yet - 请先在「团队管理」里创建一个 team,再回到工作台创建 task。 + Create a team in "Team management" first, then come back to the workbench to create a task. @@ -321,15 +321,15 @@ function BoardView({
- Task 列表 + Task list
{tasks.length === 0 ? (
- 暂无 task。点击右上角「新建 Task」创建第一个。 + No tasks yet. Click "New task" in the top right to create the first one.
) : ( @@ -361,7 +361,7 @@ function BoardView({ {canDelete && (
{/* 编辑按钮:仅 team 成员可见可点;编辑态下隐藏,由保存/取消替代 */} {!editing && canEdit && ( - )} {editing && ( <> - - + + )} -
+
{(Object.keys(STATUS_LABEL) as Task['status'][]).map((s) => { const active = task.status === s; return ( @@ -574,18 +574,18 @@ function TaskDetail({ 完成时 fire-and-forget append 到内核;append-only 语义,前端按 Set 去重。 */}
- 创建者 + Creator {task.creator_user_id} - {task.creator_user_id === currentUser && } + {task.creator_user_id === currentUser && You}
- 参与的 User + Participating users {participantUsers.length === 0 ? ( ) : ( @@ -593,17 +593,17 @@ function TaskDetail({ {u} - {u === currentUser && } + {u === currentUser && You} )) )}
- 实际参与 Agent + Actual participating agents {sessionAgents.length === 0 ? ( ) : ( @@ -611,7 +611,7 @@ function TaskDetail({ {a.name} @@ -623,14 +623,14 @@ function TaskDetail({ {/* === 描述 === */}
- 任务描述 + Task description {editing ? ( ) : (
{task.description}
@@ -641,7 +641,7 @@ function TaskDetail({ task-agent/link 的人工声明关系不再在此页面展示。 */} - 创建:{new Date(task.created_at_ms).toLocaleString()} · 更新:{new Date(task.updated_at_ms).toLocaleString()} + Created: {new Date(task.created_at_ms).toLocaleString()} · Updated: {new Date(task.updated_at_ms).toLocaleString()}
); diff --git a/MemoryPanel/web/src/services/account-store.ts b/MemoryPanel/web/src/services/account-store.ts index d38fafe..ac24ede 100644 --- a/MemoryPanel/web/src/services/account-store.ts +++ b/MemoryPanel/web/src/services/account-store.ts @@ -90,11 +90,11 @@ export function findAccountByUsername(username: string): MockAccount | null { /** 校验邮箱 + 密码登录 */ export function verifyAccountCredentials(email: string, password: string): MockAccount { const e = email.trim().toLowerCase(); - if (!e) throw new Error('请输入邮箱。'); - if (!password) throw new Error('请输入密码。'); + if (!e) throw new Error('Please enter an email address.'); + if (!password) throw new Error('Please enter a password.'); const account = readAccounts().find((a) => a.email.toLowerCase() === e); - if (!account) throw new Error(`账号不存在:${e}`); - if (account.password !== password) throw new Error('密码错误。'); + if (!account) throw new Error(`Account not found: ${e}`); + if (account.password !== password) throw new Error('Incorrect password.'); return account; } @@ -102,11 +102,11 @@ export function verifyAccountCredentials(email: string, password: string): MockA * 用户名允许重复,邮箱全局唯一。 */ export function createAccount(input: { email: string; username: string; password?: string; isAdmin?: boolean; description?: string }): MockAccount { const e = input.email.trim().toLowerCase(); - if (!e) throw new Error('邮箱不能为空。'); - if (!input.username.trim()) throw new Error('用户名不能为空。'); + if (!e) throw new Error('Email cannot be empty.'); + if (!input.username.trim()) throw new Error('Username cannot be empty.'); const accounts = readAccounts(); if (accounts.some((a) => a.email.toLowerCase() === e)) { - throw new Error(`邮箱 "${input.email}" 已被注册。`); + throw new Error(`Email "${input.email}" is already registered.`); } const account: MockAccount = { email: input.email.trim(), @@ -133,11 +133,11 @@ export function batchCreateAccounts( const e = entry.email.trim().toLowerCase(); const u = entry.username.trim(); if (!e || !u) { - errors.push({ email: entry.email || '(空)', error: '邮箱和用户名都不能为空' }); + errors.push({ email: entry.email || '(empty)', error: 'Email and username cannot be empty' }); continue; } if (emailSet.has(e)) { - errors.push({ email: entry.email, error: '邮箱已被注册' }); + errors.push({ email: entry.email, error: 'Email is already registered' }); continue; } const account: MockAccount = { @@ -160,13 +160,13 @@ export function batchCreateAccounts( /** 修改密码 */ export function changePassword(username: string, oldPassword: string, newPassword: string): void { - if (!oldPassword) throw new Error('请输入当前密码。'); - if (!newPassword) throw new Error('请输入新密码。'); - if (newPassword.length < 4) throw new Error('新密码至少需要 4 位。'); + if (!oldPassword) throw new Error('Please enter your current password.'); + if (!newPassword) throw new Error('Please enter a new password.'); + if (newPassword.length < 4) throw new Error('New password must be at least 4 characters.'); const accounts = readAccounts(); const account = accounts.find((a) => a.username === username); - if (!account) throw new Error('账号不存在。'); - if (account.password !== oldPassword) throw new Error('当前密码错误。'); + if (!account) throw new Error('Account not found.'); + if (account.password !== oldPassword) throw new Error('Current password is incorrect.'); account.password = newPassword; writeAccountsRaw(accounts); } @@ -176,12 +176,12 @@ export function changePassword(username: string, oldPassword: string, newPasswor * 权限校验在 UI 层(仅 admin 可调用)。 */ export function setAccountPassword(username: string, newPassword: string): void { - if (!newPassword) throw new Error('请输入新密码。'); - if (newPassword.length < 4) throw new Error('新密码至少需要 4 位。'); - if (!username) throw new Error('用户名不能为空。'); + if (!newPassword) throw new Error('Please enter a new password.'); + if (newPassword.length < 4) throw new Error('New password must be at least 4 characters.'); + if (!username) throw new Error('Username cannot be empty.'); const accounts = readAccounts(); const account = accounts.find((a) => a.username === username); - if (!account) throw new Error(`账号不存在:${username}`); + if (!account) throw new Error(`Account not found: ${username}`); account.password = newPassword; writeAccountsRaw(accounts); } @@ -189,14 +189,14 @@ export function setAccountPassword(username: string, newPassword: string): void /** 修改用户邮箱(admin 专有权限,权限校验在 UI 层) */ export function updateAccountEmail(username: string, newEmail: string): void { const e = newEmail.trim().toLowerCase(); - if (!e) throw new Error('邮箱不能为空。'); - if (!username) throw new Error('用户名不能为空。'); + if (!e) throw new Error('Email cannot be empty.'); + if (!username) throw new Error('Username cannot be empty.'); const accounts = readAccounts(); const account = accounts.find((a) => a.username === username); - if (!account) throw new Error(`账号不存在:${username}`); + if (!account) throw new Error(`Account not found: ${username}`); // 检查邮箱是否已被其他人使用 const conflict = accounts.find((a) => a.email.toLowerCase() === e && a.username !== username); - if (conflict) throw new Error(`邮箱 "${newEmail.trim()}" 已被其他用户使用。`); + if (conflict) throw new Error(`Email "${newEmail.trim()}" is already in use by another user.`); account.email = newEmail.trim(); writeAccountsRaw(accounts); } diff --git a/MemoryPanel/web/src/services/agent-template-store.ts b/MemoryPanel/web/src/services/agent-template-store.ts index c590d7e..ef46bda 100644 --- a/MemoryPanel/web/src/services/agent-template-store.ts +++ b/MemoryPanel/web/src/services/agent-template-store.ts @@ -40,11 +40,11 @@ const BUILTIN_AGENT_TEMPLATES: AgentTemplate[] = [ { template_id: 'builtin-pr-reviewer', name: 'PR Reviewer', - summary: '代码合入主干前的最后一道质量关卡', + summary: 'The last quality gate before code merges to main', builtin: true, - description: '执行 pr-review workflow:核对必查项 + 给出 actionable 评论。', - role_prompt: '你是严格的 PR Reviewer,是代码合入主干前的最后一道质量关卡。', - rules_prompt: '1. 先读 PR 描述与关联 issue,明确改动意图。\n2. 必查:正确性、边界条件、安全、测试覆盖、命名与可读性。\n3. 每条评论必须 actionable,指明位置与建议改法。\n4. 阻断性问题与建议性问题分开标注。', + description: 'Runs the pr-review workflow: checks required items and leaves actionable comments.', + role_prompt: 'You are a strict PR Reviewer, the last quality gate before code merges to main.', + rules_prompt: '1. Read the PR description and linked issue first to understand intent.\n2. Required checks: correctness, edge cases, security, test coverage, naming and readability.\n3. Every comment must be actionable, pointing to the location and a suggested fix.\n4. Separate blocking issues from suggestions.', skills: [], code_graphs: [], llm_wikis: [], @@ -53,12 +53,12 @@ const BUILTIN_AGENT_TEMPLATES: AgentTemplate[] = [ }, { template_id: 'builtin-bugfix-engineer', - name: 'Bug-fix 工程师', - summary: '复现 → 定位 → 修复 → 自测的修复 loop', + name: 'Bug-fix Engineer', + summary: 'Reproduce → locate → fix → self-test loop', builtin: true, - description: '面向 bug-fix loop 的工程师 agent:复现 → 定位 → 修复 → 自测。', - role_prompt: '你是面向 bug-fix loop 的修复工程师,对每个缺陷负责到根因,追求最小且可验证的修复。', - rules_prompt: '1. 先稳定复现,再动手;无法复现先补复现信息。\n2. 定位根因而非掩盖症状。\n3. 修复保持最小 diff,附带回归测试。\n4. 自测通过后再提交,说明验证方式。', + description: 'An engineer agent for the bug-fix loop: reproduce → locate → fix → self-test.', + role_prompt: 'You are a bug-fix engineer for the bug-fix loop, owning each defect down to its root cause and aiming for the smallest verifiable fix.', + rules_prompt: '1. Get a stable repro before touching anything; if it cannot be reproduced, gather repro info first.\n2. Locate the root cause rather than masking the symptom.\n3. Keep the fix a minimal diff with an accompanying regression test.\n4. Self-test before submitting, and state how it was verified.', skills: [], code_graphs: [], llm_wikis: [], @@ -67,12 +67,12 @@ const BUILTIN_AGENT_TEMPLATES: AgentTemplate[] = [ }, { template_id: 'builtin-issue-triage', - name: 'Issue 分诊员', - summary: '新进 issue 的第一接待人:分类 / 补全 / 指派', + name: 'Issue Triager', + summary: 'First responder for incoming issues: classify / complete / assign', builtin: true, - description: '面向新进 issue:判断类型、补充复现信息、指派 owner。', - role_prompt: '你是 issue 分诊员,是新进 issue 的第一接待人,负责分类、补全信息并指派。', - rules_prompt: '1. 判断类型(bug / feature / question / 重复)。\n2. 缺信息时按模板向报告者追问复现步骤、环境、期望。\n3. 标注优先级与影响面。\n4. 指派合适 owner 并说明理由。', + description: 'For incoming issues: determines the type, fills in missing repro info, and assigns an owner.', + role_prompt: 'You are an issue triager, the first responder for incoming issues, responsible for classifying, completing information, and assigning them.', + rules_prompt: '1. Determine the type (bug / feature / question / duplicate).\n2. When info is missing, use a template to ask the reporter for repro steps, environment, and expected behavior.\n3. Label priority and impact.\n4. Assign a suitable owner with reasoning.', skills: [], code_graphs: [], llm_wikis: [], @@ -81,12 +81,12 @@ const BUILTIN_AGENT_TEMPLATES: AgentTemplate[] = [ }, { template_id: 'builtin-doc-engineer', - name: '文档工程师', - summary: '随 PR 同步更新 wiki / changelog', + name: 'Documentation Engineer', + summary: 'Keeps the wiki / changelog updated alongside each PR', builtin: true, - description: '随 PR 同步更新 wiki / changelog,保持团队知识库与代码一致。', - role_prompt: '你是文档工程师,确保团队知识库与代码始终保持同步、可信。', - rules_prompt: '1. 每个会影响行为的 PR 都要评估文档影响。\n2. 更新 changelog,语言面向使用者而非实现者。\n3. 失效文档及时下线或标注。\n4. 文档需可被检索,附必要链接与示例。', + description: 'Updates wiki / changelog alongside each PR, keeping the team knowledge base consistent with the code.', + role_prompt: 'You are a documentation engineer, ensuring the team knowledge base stays synced with the code and trustworthy at all times.', + rules_prompt: "1. Assess the documentation impact of every PR that changes behavior.\n2. Update the changelog in language aimed at users, not implementers.\n3. Retire or flag stale docs promptly.\n4. Docs must be searchable, with necessary links and examples.", skills: [], code_graphs: [], llm_wikis: [], @@ -128,7 +128,7 @@ export function createAgentTemplate(input: { chat_memories?: string[]; }): AgentTemplate { const name = input.name.trim(); - if (!name) throw new Error('createAgentTemplate: 模板名不能为空。'); + if (!name) throw new Error('createAgentTemplate: template name cannot be empty.'); const now = Date.now(); const tpl: AgentTemplate = { template_id: `tpl_${now}_${Math.random().toString(36).slice(2, 8)}`, diff --git a/MemoryPanel/web/src/stores/backend.ts b/MemoryPanel/web/src/stores/backend.ts index af199c1..9651c4a 100644 --- a/MemoryPanel/web/src/stores/backend.ts +++ b/MemoryPanel/web/src/stores/backend.ts @@ -118,7 +118,7 @@ export const useBackendStore = create((set, get) => ({ } catch (err) { console.error('[backend store] fetchTeams failed:', err); set({ teamsLoading: false }); - tea.notify.error('加载团队列表失败'); + tea.notify.error('Failed to load team list'); } finally { set({ inflightTeams: null }); } @@ -158,7 +158,7 @@ export const useBackendStore = create((set, get) => ({ Object.entries(s.inflightAgents).filter(([k]) => k !== teamId) ), })); - tea.notify.error('加载 Agent 列表失败'); + tea.notify.error('Failed to load agent list'); return []; } })(); @@ -204,7 +204,7 @@ export const useBackendStore = create((set, get) => ({ Object.entries(s.inflightTasks).filter(([k]) => k !== teamId) ), })); - tea.notify.error('加载任务列表失败'); + tea.notify.error('Failed to load task list'); return []; } })(); diff --git a/deploy/global-images/start-memory-hub.sh b/deploy/global-images/start-memory-hub.sh index 106724a..0b95137 100755 --- a/deploy/global-images/start-memory-hub.sh +++ b/deploy/global-images/start-memory-hub.sh @@ -41,6 +41,19 @@ fi rm_container_if_exists "$CONTAINER" +# GODCALL: optional git credentials for private Gitea imports. +# Set GIT_CREDENTIALS_FILE in .env to a git credential-store file +# (one line, e.g. http://user:token@gitea-host:3000). It is mounted read-only +# so tokens never appear in repo URLs, the panel DB, or docker inspect env. +GIT_CRED_ARGS=() +if [[ -n "${GIT_CREDENTIALS_FILE:-}" && -s "$GIT_CREDENTIALS_FILE" ]]; then + GITCONFIG_FILE="$SCRIPT_DIR/.godcall-gitconfig" + printf '[credential]\n\thelper = store --file /root/.git-credentials\n' > "$GITCONFIG_FILE" + GIT_CRED_ARGS+=( -v "$GIT_CREDENTIALS_FILE:/root/.git-credentials:ro" \ + -v "$GITCONFIG_FILE:/root/.gitconfig:ro" ) + info "git credentials mounted from $GIT_CREDENTIALS_FILE" +fi + # 内部 knowledge 通过 upstream memory 调 LLM 走 custom 模式,直接指向 MEMORY_LLM_* # LLM_MODE=custom → 不走 memory 的 LLM proxy,而是 knowledge 直连用户提供的端点 info "启动 memory-hub (image=$MEMORY_HUB_IMAGE, panel=$PANEL_PORT knowledge=$KNOWLEDGE_PORT)" @@ -51,6 +64,9 @@ $DOCKER run -d --name "$CONTAINER" \ -p "${PANEL_PORT}:8125" \ -p "${KNOWLEDGE_PORT}:8424" \ -v "${PANEL_VOLUME}:/data/knowledge" \ + ${GIT_CRED_ARGS[@]+"${GIT_CRED_ARGS[@]}"} \ + -e KNOWLEDGE_ALLOW_HTTP="${KNOWLEDGE_ALLOW_HTTP:-}" \ + -e KNOWLEDGE_SSRF_CHECK="${KNOWLEDGE_SSRF_CHECK:-}" \ -e PANEL_PORT=8125 \ -e KNOWLEDGE_PORT=8424 \ -e KNOWLEDGE_PUBLIC_BASE_URL="$KNOWLEDGE_PUBLIC_BASE_URL" \ diff --git a/deploy/panel-knowledge-combined/Dockerfile b/deploy/panel-knowledge-combined/Dockerfile index a9d9cdb..88a602a 100644 --- a/deploy/panel-knowledge-combined/Dockerfile +++ b/deploy/panel-knowledge-combined/Dockerfile @@ -1,7 +1,7 @@ FROM node:22-slim AS base -RUN sed -i 's|deb.debian.org|mirrors.tencent.com|g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || \ - sed -i 's|deb.debian.org|mirrors.tencent.com|g' /etc/apt/sources.list 2>/dev/null || true +# GODCALL: upstream rewrote apt sources to mirrors.tencent.com here — unreachable +# outside Tencent's network; use stock Debian mirrors. RUN apt-get update && apt-get install -y --no-install-recommends \ python3 make g++ git curl ca-certificates \ diff --git a/tools/import-gitea.mjs b/tools/import-gitea.mjs new file mode 100644 index 0000000..39f324d --- /dev/null +++ b/tools/import-gitea.mjs @@ -0,0 +1,87 @@ +#!/usr/bin/env node +/** + * GODCALL — bulk-import every Gitea repo as a CodeGraph. + * + * Enumerates repos via the Gitea API and registers each with the knowledge + * service. Clone auth is handled by the git credential store mounted into the + * memory-hub container (see start-memory-hub.sh GIT_CREDENTIALS_FILE) — tokens + * never go into repo URLs. + * + * Env: + * GITEA_URL e.g. http://100.71.119.27:3000 + * GITEA_TOKEN_FILE file containing a Gitea access token (read:repository) + * KNOWLEDGE_URL e.g. http://100.91.239.7:8424 + * TEAM_ID godcall team id (team-...) + * USER_ID godcall user id (usr-...) + * GITEA_CLONE_BASE optional override when Gitea ROOT_URL differs from the + * address reachable from the hub container + * ONLY optional comma-separated repo names to import + * SKIP optional comma-separated repo names to skip + */ +import { readFileSync } from "node:fs"; + +const need = (k) => { + const v = process.env[k]; + if (!v) { console.error(`missing env ${k}`); process.exit(1); } + return v; +}; + +const GITEA_URL = need("GITEA_URL").replace(/\/$/, ""); +const TOKEN = readFileSync(need("GITEA_TOKEN_FILE"), "utf8").trim(); +const KNOWLEDGE_URL = need("KNOWLEDGE_URL").replace(/\/$/, ""); +const TEAM_ID = need("TEAM_ID"); +const USER_ID = need("USER_ID"); +const CLONE_BASE = (process.env.GITEA_CLONE_BASE || GITEA_URL).replace(/\/$/, ""); +const ONLY = (process.env.ONLY || "").split(",").map(s => s.trim()).filter(Boolean); +const SKIP = new Set((process.env.SKIP || "").split(",").map(s => s.trim()).filter(Boolean)); + +async function giteaRepos() { + const repos = []; + for (let page = 1; ; page++) { + const r = await fetch(`${GITEA_URL}/api/v1/repos/search?limit=50&page=${page}`, { + headers: { Authorization: `token ${TOKEN}` }, + }); + if (!r.ok) throw new Error(`gitea search ${r.status}: ${await r.text()}`); + const { data } = await r.json(); + if (!data?.length) break; + repos.push(...data); + if (data.length < 50) break; + } + return repos; +} + +async function createCodeGraph(repo) { + const repoUrl = `${CLONE_BASE}/${repo.full_name}.git`; + const body = { + team_id: TEAM_ID, + user_id: USER_ID, + repo_url: repoUrl, + branch: repo.default_branch || "main", + repo_name: repo.name, + }; + const r = await fetch(`${KNOWLEDGE_URL}/v3/code-graph/create`, { + method: "POST", + headers: { "Content-Type": "application/json", "x-tdai-service-id": "default" }, + body: JSON.stringify(body), + }); + const json = await r.json().catch(() => ({})); + return { status: r.status, id: json?.data?.code_graph_id, msg: json?.message }; +} + +const repos = await giteaRepos(); +console.log(`gitea reports ${repos.length} repos`); +let ok = 0, fail = 0; +for (const repo of repos) { + if (ONLY.length && !ONLY.includes(repo.name)) continue; + if (SKIP.has(repo.name)) { console.log(`skip ${repo.full_name}`); continue; } + try { + const res = await createCodeGraph(repo); + const good = res.status === 200 || res.status === 201; + good ? ok++ : fail++; + console.log(`${good ? "ok " : "FAIL"} ${repo.full_name} [${repo.default_branch}] -> ${res.id ?? res.msg ?? res.status}`); + } catch (e) { + fail++; + console.log(`FAIL ${repo.full_name}: ${e.message}`); + } +} +console.log(`done: ${ok} registered, ${fail} failed`);