Files
memind/user-memory-profile.mjs
T
John 6ee6fd64dd Add MindSpace page live edit, chat skills, and H5 deploy tooling.
Introduce page edit sessions with draft preview and patch API, chat skill picker, user memory profile, h5ApiBase resolution, voice WAV transport, and scripts for 105/g2 deployment and Plaza local dev.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 22:09:38 -07:00

179 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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');
}