import fs from 'node:fs'; import path from 'node:path'; import { resolveUserAddressName } from './user-publish.mjs'; export const USER_MEMORY_PROFILE_FILENAME = '.tkmind-profile.json'; export const USER_MEMORY_PROFILE_VERSION = 1; export function buildInitialUserMemoryProfile({ userId, displayName, username, slug, now = Date.now(), }) { const addressName = resolveUserAddressName({ displayName, username, slug }); return { version: USER_MEMORY_PROFILE_VERSION, userId, displayName: addressName, language: 'zh-CN', responseStyle: 'balanced', preferences: [], createdAt: now, updatedAt: now, }; } export function resolveUserMemoryProfilePath(workspaceRoot) { return path.join(workspaceRoot, USER_MEMORY_PROFILE_FILENAME); } export function loadUserMemoryProfile(workspaceRoot) { const profilePath = resolveUserMemoryProfilePath(workspaceRoot); if (!fs.existsSync(profilePath)) return null; try { const raw = fs.readFileSync(profilePath, 'utf8'); const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== 'object') return null; return parsed; } catch { return null; } } export function ensureUserMemoryProfile(workspaceRoot, context) { const profilePath = resolveUserMemoryProfilePath(workspaceRoot); const existing = loadUserMemoryProfile(workspaceRoot); if (existing) { const addressName = resolveUserAddressName(context); const nextDisplayName = addressName || existing.displayName; if (nextDisplayName && nextDisplayName !== existing.displayName) { const updated = { ...existing, displayName: nextDisplayName, updatedAt: Date.now(), }; fs.writeFileSync(profilePath, `${JSON.stringify(updated, null, 2)}\n`, 'utf8'); return updated; } return existing; } const profile = buildInitialUserMemoryProfile({ userId: context.userId, displayName: context.displayName, username: context.username, slug: context.slug, }); fs.writeFileSync(profilePath, `${JSON.stringify(profile, null, 2)}\n`, 'utf8'); return profile; } export function renderMemoryStoreGuidance({ addressName }) { const name = addressName || '用户'; return [ '## TKMind 长期记忆(L3 / memory 扩展)', '', `- 当前服务对象:**${name}**(仅此用户,不得与其他用户混淆)`, '- 使用 memory 工具记录用户**明确表达**的偏好、习惯与稳定事实', '- 记忆分类建议:', ' - `preference`:回复风格、格式、语言、称呼习惯', ' - `project`:当前项目目标、约定、技术选型(与 L2 项目记忆互补)', ' - `fact`:长期稳定、可复用的事实(如部门、常用工具)', '- 用户说「忘记…」「不要再…」「取消偏好…」时,必须更新或删除对应记忆', '- **禁止**记录其他用户的信息;**禁止**猜测未明确表达的偏好', '- 与 L2 项目记忆(harness)分工:L2 记近期工作与决策摘要;L3 记可跨会话复用的个人偏好', ].join('\n'); } function renderPreferenceLines(profile) { const items = Array.isArray(profile?.preferences) ? profile.preferences : []; if (items.length === 0) { return ['- (暂无结构化偏好 — 可在对话中用 memory 工具补充)']; } return items.map((item) => { if (typeof item === 'string') return `- ${item}`; const category = item?.category ? `[${item.category}] ` : ''; const label = item?.label ?? item?.key ?? 'preference'; const value = item?.value ?? item?.content ?? ''; return `- ${category}${label}:${value}`; }); } export function renderUserMemoryProfileForHarness(profile, context = {}) { const addressName = resolveUserAddressName({ displayName: profile?.displayName ?? context.displayName, username: context.username, slug: context.slug, }); const language = profile?.language === 'zh-CN' ? '简体中文' : (profile?.language ?? '简体中文'); const styleMap = { concise: '简洁直接,少废话', detailed: '详细完整,必要时展开', balanced: '简洁务实,必要时补充细节', }; const responseStyle = styleMap[profile?.responseStyle] ?? styleMap.balanced; return [ '## TKMind 用户偏好画像(L3)', '', `- 用户称呼:**${addressName}**`, `- 界面语言:${language}`, `- 默认回复风格:${responseStyle}`, '', '### 已记录的结构化偏好', ...renderPreferenceLines(profile), ].join('\n'); } export function buildSessionMemoryEntries({ workingDir, sessionPolicy, sandboxConstraints = null, userContext = null, }) { const entries = []; if (sandboxConstraints?.trim()) { entries.push({ title: 'TKMind 用户空间沙箱', content: sandboxConstraints.trim(), }); } if (!hasMemoryStore(sessionPolicy)) { return entries; } const profile = loadUserMemoryProfile(workingDir) ?? (userContext?.userId ? ensureUserMemoryProfile(workingDir, userContext) : null); const addressName = resolveUserAddressName({ displayName: profile?.displayName ?? userContext?.displayName, username: userContext?.username, slug: userContext?.slug, }); entries.push({ title: 'TKMind 长期记忆规则', content: renderMemoryStoreGuidance({ addressName }), }); if (profile) { entries.push({ title: 'TKMind 用户偏好画像', content: renderUserMemoryProfileForHarness(profile, userContext ?? {}), }); } return entries; } export function hasMemoryStore(sessionPolicy) { return (sessionPolicy?.extensionOverrides ?? []).some((ext) => ext.name === 'memory'); }