fix(mindspace): edit_file 落盘、Finish 聊天 merge 与回归守卫
- finish-sync 支持 edit_file 覆盖 public HTML - Finish 同步 merge 本地流式消息,剥离 agent 内部前缀 - 新增 verify:mindspace-publish-guards 与 AGENTS.md 跨工具说明 - 发版脚本接入回归门禁;103 runtime 发布含备份回退 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+46
-58
@@ -14,6 +14,11 @@ import {
|
||||
} from './auth.mjs';
|
||||
import { createDbPool, initSchema, isDatabaseConfigured } from './db.mjs';
|
||||
import { createAgentRunGateway } from './agent-run-gateway.mjs';
|
||||
import {
|
||||
createAgentRunEventsHandler,
|
||||
createGetAgentRunHandler,
|
||||
createPostAgentRunsHandler,
|
||||
} from './agent-run-routes.mjs';
|
||||
import { createTkmindProxy, sanitizeSessionConversationPublicHtmlLinks } from './tkmind-proxy.mjs';
|
||||
import {
|
||||
clearUserSessionCookie,
|
||||
@@ -78,7 +83,10 @@ import {
|
||||
resolveChatSaveAnalysis,
|
||||
resolveStaticHtmlContent,
|
||||
} from './mindspace-chat-save.mjs';
|
||||
import { syncPublicHtmlAfterFinish } from './mindspace-public-finish-sync.mjs';
|
||||
import {
|
||||
materializePublicHtmlWritesFromSessionEvent,
|
||||
syncPublicHtmlAfterFinish,
|
||||
} from './mindspace-public-finish-sync.mjs';
|
||||
import { syncGeneratedPagesFromPublicAssets } from './mindspace-page-sync.mjs';
|
||||
import { generateHtmlThumbnail } from './mindspace-thumbnails.mjs';
|
||||
import { injectOgTags, injectWechatShareBridge } from './mindspace-og-tags.mjs';
|
||||
@@ -3903,38 +3911,7 @@ api.post('/agent/runs', async (req, res, next) => {
|
||||
[
|
||||
tkmindProxy.requireUser,
|
||||
tkmindProxy.ensureChatAllowed,
|
||||
async (request, response) => {
|
||||
try {
|
||||
const sessionId = String(request.body?.session_id ?? '').trim() || null;
|
||||
const requestId = String(request.body?.request_id ?? '').trim();
|
||||
const userMessage = request.body?.user_message ?? null;
|
||||
if (!requestId) {
|
||||
response.status(400).json({ message: '缺少 request_id' });
|
||||
return;
|
||||
}
|
||||
if (!userMessage) {
|
||||
response.status(400).json({ message: '缺少 user_message' });
|
||||
return;
|
||||
}
|
||||
if (sessionId) {
|
||||
const owns = await userAuth.ownsSession(request.currentUser.id, sessionId);
|
||||
if (!owns) {
|
||||
response.status(403).json({ message: '无权访问该会话' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
const run = await agentRunGateway.createRun(request.currentUser.id, {
|
||||
sessionId,
|
||||
requestId,
|
||||
userMessage,
|
||||
});
|
||||
response.status(202).json({ run });
|
||||
} catch (err) {
|
||||
response.status(500).json({
|
||||
message: err instanceof Error ? err.message : '创建任务失败',
|
||||
});
|
||||
}
|
||||
},
|
||||
createPostAgentRunsHandler({ userAuth, agentRunGateway }),
|
||||
],
|
||||
req,
|
||||
res,
|
||||
@@ -3948,26 +3925,21 @@ api.get('/agent/runs/:runId', async (req, res, next) => {
|
||||
return runHandlerChain(
|
||||
[
|
||||
tkmindProxy.requireUser,
|
||||
async (request, response) => {
|
||||
try {
|
||||
const run = await agentRunGateway.getRunForUser(
|
||||
request.currentUser.id,
|
||||
request.params.runId,
|
||||
);
|
||||
if (!run) {
|
||||
response.status(404).json({ message: '任务不存在' });
|
||||
return;
|
||||
}
|
||||
if (run.status !== 'succeeded' && run.status !== 'failed') {
|
||||
agentRunGateway.dispatchRun(run.id);
|
||||
}
|
||||
response.json({ run });
|
||||
} catch (err) {
|
||||
response.status(500).json({
|
||||
message: err instanceof Error ? err.message : '读取任务失败',
|
||||
});
|
||||
}
|
||||
},
|
||||
createGetAgentRunHandler({ agentRunGateway }),
|
||||
],
|
||||
req,
|
||||
res,
|
||||
next,
|
||||
);
|
||||
});
|
||||
|
||||
api.get('/agent/runs/:runId/events', async (req, res, next) => {
|
||||
await userAuthReady;
|
||||
if (!userAuth || !tkmindProxy || !agentRunGateway) return next();
|
||||
return runHandlerChain(
|
||||
[
|
||||
tkmindProxy.requireUser,
|
||||
createAgentRunEventsHandler({ agentRunGateway }),
|
||||
],
|
||||
req,
|
||||
res,
|
||||
@@ -4004,9 +3976,11 @@ api.get('/sessions/:sessionId', async (req, res, next) => {
|
||||
if (sessionSnapshotService?.isEnabled()) {
|
||||
const snapshot = await sessionSnapshotService.get(sessionId);
|
||||
if (snapshot) {
|
||||
const mcMatch = hintMc == null || snapshot.meta.synced_msg_count === hintMc;
|
||||
const uaMatch = hintUa == null || snapshot.meta.source_updated_at === hintUa;
|
||||
if (mcMatch && uaMatch) {
|
||||
// REGRESSION GUARD: without both hints, stale snapshot can wipe mid-turn chat.
|
||||
const canUseSnapshotCache = hintMc != null && hintUa != null;
|
||||
const mcMatch = snapshot.meta.synced_msg_count === hintMc;
|
||||
const uaMatch = snapshot.meta.source_updated_at === hintUa;
|
||||
if (canUseSnapshotCache && mcMatch && uaMatch) {
|
||||
const sanitizedMessages = sanitizeSessionConversationPublicHtmlLinks(
|
||||
snapshot.messages,
|
||||
req.currentUser,
|
||||
@@ -4094,6 +4068,10 @@ api.get('/sessions/:sessionId/events', async (req, res, next) => {
|
||||
if (!owns) {
|
||||
return res.status(403).json({ message: '无权访问该会话' });
|
||||
}
|
||||
const publishDir = resolvePublishDir(__dirname, { id: req.currentUser.id });
|
||||
const syncPublicHtmlDuringStream = (event) => {
|
||||
materializePublicHtmlWritesFromSessionEvent(event, { publishDir });
|
||||
};
|
||||
// After Finish, refresh the snapshot and persist any newly generated public
|
||||
// workspace HTML into the asset store before a later restart rebuilds the
|
||||
// workspace from DB-backed assets only.
|
||||
@@ -4122,7 +4100,7 @@ api.get('/sessions/:sessionId/events', async (req, res, next) => {
|
||||
await syncPublicHtmlAfterFinish({
|
||||
messages,
|
||||
currentUser: req.currentUser,
|
||||
publishDir: resolvePublishDir(__dirname, { id: uid }),
|
||||
publishDir,
|
||||
syncWorkspaceAssets:
|
||||
WORKSPACE_MAINTENANCE_ENABLED && mindSpaceAssets
|
||||
? (userId, options) => mindSpaceAssets.syncWorkspaceAssets(userId, options)
|
||||
@@ -4130,7 +4108,10 @@ api.get('/sessions/:sessionId/events', async (req, res, next) => {
|
||||
});
|
||||
await syncUserGeneratedPages(uid);
|
||||
};
|
||||
return tkmindProxy.proxySessionEvents(req, res, sessionId, { onAfterFinish });
|
||||
return tkmindProxy.proxySessionEvents(req, res, sessionId, {
|
||||
onAfterFinish,
|
||||
onEvent: syncPublicHtmlDuringStream,
|
||||
});
|
||||
});
|
||||
|
||||
api.use(async (req, res, next) => {
|
||||
@@ -4141,6 +4122,13 @@ api.use(async (req, res, next) => {
|
||||
return sendError(res, req, 404, 'not_found', `接口不存在:${req.method} ${req.path}`);
|
||||
}
|
||||
|
||||
if (req.method === 'GET' && /^\/agent\/runs\/[^/]+\/events$/.test(req.path)) {
|
||||
return res.status(503).json({
|
||||
message: 'Agent Run SSE 接口需在 Portal 本地处理,请确认 server.mjs 已更新并重启后端',
|
||||
code: 'AGENT_RUNS_NATIVE_REQUIRED',
|
||||
});
|
||||
}
|
||||
|
||||
const sessionMatch = req.path.match(/^\/sessions\/([^/]+)/);
|
||||
if (sessionMatch) {
|
||||
const sessionId = sessionMatch[1];
|
||||
|
||||
Reference in New Issue
Block a user