6ee6fd64dd
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>
179 lines
5.6 KiB
JavaScript
179 lines
5.6 KiB
JavaScript
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');
|
||
}
|