Files
memind/mindspace-page-live-edit.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

105 lines
3.5 KiB
JavaScript

import { mergeMindSpacePagePatch, normalizeMindSpacePagePatchInput } from './mindspace-page-patch.mjs';
export function createPageLiveEditService({ pageService, resolveUserIdForAgentSession }) {
/** @type {Map<string, { userId: string, pageId: string, boundAt: number }>} */
const sessionBindings = new Map();
/** @type {Map<string, number>} */
const liveRevisions = new Map();
const bumpRevision = (pageId) => {
liveRevisions.set(pageId, (liveRevisions.get(pageId) ?? 0) + 1);
};
return {
bindSession({ userId, sessionId, pageId, parentSessionId = null }) {
if (!userId || !sessionId || !pageId) {
throw Object.assign(new Error('缺少绑定参数'), { code: 'invalid_request' });
}
sessionBindings.set(String(sessionId), {
userId: String(userId),
pageId: String(pageId),
parentSessionId: parentSessionId ? String(parentSessionId) : null,
boundAt: Date.now(),
});
return {
sessionId: String(sessionId),
pageId: String(pageId),
...(parentSessionId ? { parentSessionId: String(parentSessionId) } : {}),
};
},
unbindSession(sessionId) {
sessionBindings.delete(String(sessionId));
},
getBinding(sessionId) {
return sessionBindings.get(String(sessionId)) ?? null;
},
getLiveRevision(pageId) {
return liveRevisions.get(String(pageId)) ?? 0;
},
async applyAgentPatch(input) {
const sessionId = String(input?.sessionId ?? input?.session_id ?? '').trim();
const pageId = String(input?.pageId ?? input?.page_id ?? '').trim();
if (!sessionId || !pageId) {
throw Object.assign(new Error('缺少 session_id 或 page_id'), { code: 'invalid_request' });
}
const userId = await resolveUserIdForAgentSession(sessionId);
if (!userId) {
throw Object.assign(new Error('无效的 Agent 会话'), { code: 'forbidden' });
}
const binding = sessionBindings.get(sessionId);
if (binding && binding.pageId !== pageId) {
throw Object.assign(new Error('当前会话未绑定该页面'), { code: 'page_binding_mismatch' });
}
if (binding && binding.userId !== userId) {
throw Object.assign(new Error('会话与用户不匹配'), { code: 'forbidden' });
}
const patch = normalizeMindSpacePagePatchInput(input);
if (!patch) {
throw Object.assign(new Error('缺少 title / summary / content 变更'), { code: 'invalid_request' });
}
const page = await pageService.getPage(userId, pageId);
const merged = mergeMindSpacePagePatch(
{
title: page.title,
summary: page.summary ?? '',
content: page.content ?? '',
},
patch,
);
const updated = await pageService.updatePage(userId, pageId, {
expectedVersion: input?.expectedVersion ?? input?.expected_version ?? page.versionNo,
title: merged.title,
summary: merged.summary,
content: merged.content,
templateId: page.templateId,
changeNote: String(input?.changeNote ?? input?.change_note ?? 'Agent 对话修改').slice(0, 255),
});
bumpRevision(pageId);
return {
page: updated,
liveRevision: liveRevisions.get(pageId) ?? 0,
};
},
async getRevisionSnapshot(userId, pageId) {
const page = await pageService.getPage(userId, pageId);
return {
pageId: page.id,
versionNo: page.versionNo,
updatedAt: page.updatedAt,
liveRevision: liveRevisions.get(pageId) ?? 0,
};
},
};
}