import { Agent, fetch as undiciFetch } from 'undici'; import { buildPageEditAgentPolicy } from './capabilities.mjs'; import { buildPageEditSubAgentInstructions } from './mindspace-chat-context.mjs'; import { buildMindSpacePageSaveInstructions } from './mindspace-page-patch.mjs'; import { reconcileAgentSession } from './session-reconcile.mjs'; import { resolveSessionAccess } from './session-broker.mjs'; const insecureDispatcher = new Agent({ connect: { rejectUnauthorized: false }, }); function isHttpsTarget(target) { return target.startsWith('https://'); } function createApiFetch(apiTarget, apiSecret) { return async (pathname, init = {}) => { const url = new URL(pathname, apiTarget); const headers = { ...(init.headers ?? {}), 'X-Secret-Key': apiSecret, }; if (init.body && !headers['Content-Type']) { headers['Content-Type'] = 'application/json'; } return undiciFetch(url, { ...init, headers, dispatcher: isHttpsTarget(apiTarget) ? insecureDispatcher : undefined, }); }; } async function readJson(upstream) { const text = await upstream.text(); if (!upstream.ok) { throw Object.assign(new Error(text || `upstream ${upstream.status}`), { code: 'worker_unavailable', }); } return text ? JSON.parse(text) : null; } function buildPageEditSeedContent({ page, parentSessionId, h5ApiBase, sessionId }) { const context = { spaceId: 'mindspace', spaceName: 'MindSpace', view: 'page', route: `/mindspace/page/${page.id}/preview`, pageEditMode: true, parentAgentSessionId: parentSessionId, agentSessionId: sessionId, h5ApiBase, page: { id: page.id, title: page.title, summary: page.summary ?? '', status: page.status, versionNo: page.versionNo, templateId: page.templateId, contentFormat: page.contentFormat ?? 'html', }, }; const lines = [ ...buildPageEditSubAgentInstructions(context).split('\n'), '[MindSpace 页面编辑子会话]', `- 页面:${page.title}(id: ${page.id})`, `- 内容格式:${page.contentFormat ?? 'html'}`, `- 当前版本:v${page.versionNo ?? 1}`, ...buildMindSpacePageSaveInstructions( { id: page.id, contentFormat: page.contentFormat ?? 'html', }, { sessionId, h5ApiBase }, ), '请用一句话确认你已准备好修改此页面,并等待用户指令。', ]; return lines.join('\n'); } export function buildPageEditSessionConstraints(pageTitle) { return [ '【页面编辑子 Agent 沙箱】', `- 当前任务:仅修改 MindSpace 页面「${pageTitle}」`, '- 禁止读写工作区其它文件;禁止 delegate / load_skill / 发布新页面', '- 改页面优先 curl 调用 mindspace_page_patch;若无 shell 权限则在回复末尾输出 ```mindspace-page-update``` 块', '- 每次改动后立即触发预览更新,不要只口头说「已修改」', ].join('\n'); } export function createPageEditSessionService({ apiTarget, apiSecret, userAuth, sessionAccess = null, pageService, pageLiveEdit, llmProviderService, }) { const sessionStore = resolveSessionAccess({ userAuth, sessionAccess }); const apiFetch = createApiFetch(apiTarget, apiSecret); const applySessionLlmProvider = async (sessionId) => { if (!llmProviderService || !sessionId) return null; try { return await llmProviderService.applyBestProviderForSession(sessionId); } catch { return null; } }; return { async forkSession({ userId, pageId, parentSessionId, h5ApiBase = null }) { if (!userId || !pageId || !parentSessionId) { throw Object.assign(new Error('缺少 fork 参数'), { code: 'invalid_request' }); } const ownsParent = await sessionStore.validateOwnership(userId, parentSessionId); if (!ownsParent) { throw Object.assign(new Error('无权使用父会话'), { code: 'forbidden' }); } const page = await pageService.getPage(userId, pageId); if (page.contentFormat !== 'html') { throw Object.assign(new Error('仅 HTML 页面支持编辑子会话'), { code: 'invalid_request' }); } const gate = await userAuth.canUseChat(userId); if (!gate.ok) { throw Object.assign(new Error(gate.message || '当前用户无法使用 Agent'), { code: 'forbidden' }); } const workingDir = await userAuth.resolveWorkingDir(userId); const basePolicy = await userAuth.getAgentSessionPolicy(userId); const sessionPolicy = buildPageEditAgentPolicy(basePolicy); const publishLayout = await userAuth.getUserPublishLayout(userId); const startSession = await readJson( await apiFetch('/agent/start', { method: 'POST', body: JSON.stringify({ working_dir: workingDir, enable_context_memory: sessionPolicy.enableContextMemory, ...(sessionPolicy.extensionOverrides ? { extension_overrides: sessionPolicy.extensionOverrides } : {}), }), }), ); const sessionId = startSession?.id; if (!sessionId) { throw Object.assign(new Error('Agent 会话启动失败'), { code: 'worker_unavailable' }); } await sessionStore.registerAgentSession(userId, sessionId); if (sessionPolicy.gooseMode) { await readJson( await apiFetch('/agent/update_session', { method: 'POST', body: JSON.stringify({ session_id: sessionId, goose_mode: sessionPolicy.gooseMode, }), }), ); } const sandboxConstraints = [ publishLayout?.constraints ?? '', buildPageEditSessionConstraints(page.title), ] .filter(Boolean) .join('\n\n'); await reconcileAgentSession(apiFetch, sessionId, { workingDir, sessionPolicy, sandboxConstraints, userContext: publishLayout ? { userId, displayName: publishLayout.displayName, username: publishLayout.username, slug: publishLayout.slug, } : null, }); await applySessionLlmProvider(sessionId); await pageLiveEdit.bindSession({ userId, sessionId, pageId, parentSessionId, }); const seed = buildPageEditSeedContent({ page, parentSessionId, h5ApiBase, sessionId, }); await apiFetch('/agent/harness_remember', { method: 'POST', body: JSON.stringify({ sessionId, title: `页面编辑 · ${page.title}`, content: seed, }), }); await apiFetch('/agent/harness_bootstrap', { method: 'POST', body: JSON.stringify({ sessionId, force: true }), }); return { sessionId, pageId, parentSessionId, }; }, async closeSession({ userId, sessionId, pageId, parentSessionId, summary = '' }) { if (!userId || !sessionId || !pageId) { throw Object.assign(new Error('缺少 close 参数'), { code: 'invalid_request' }); } const owns = await sessionStore.validateOwnership(userId, sessionId); if (!owns) { throw Object.assign(new Error('无权关闭该子会话'), { code: 'forbidden' }); } const binding = pageLiveEdit.getBinding(sessionId); if (binding && binding.pageId !== String(pageId)) { throw Object.assign(new Error('子会话未绑定该页面'), { code: 'page_binding_mismatch' }); } pageLiveEdit.unbindSession(sessionId); const trimmedSummary = String(summary ?? '').trim(); const parentId = String(parentSessionId ?? binding?.parentSessionId ?? '').trim(); if (trimmedSummary && parentId) { const ownsParent = await sessionStore.validateOwnership(userId, parentId); if (ownsParent) { const page = await pageService.getPage(userId, pageId).catch(() => null); await apiFetch('/agent/harness_remember', { method: 'POST', body: JSON.stringify({ sessionId: parentId, title: `页面编辑摘要 · ${page?.title ?? pageId}`, content: trimmedSummary, }), }); } } return { sessionId, pageId, merged: Boolean(trimmedSummary && parentId) }; }, }; } export const pageEditSessionInternals = { buildPageEditSeedContent, buildPageEditSessionConstraints, };