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:
@@ -0,0 +1,135 @@
|
||||
export function createPostAgentRunsHandler({ userAuth, agentRunGateway }) {
|
||||
return async function postAgentRuns(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 : '创建任务失败',
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function createGetAgentRunHandler({ agentRunGateway }) {
|
||||
return async function getAgentRun(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 : '读取任务失败',
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function createAgentRunEventsHandler({
|
||||
agentRunGateway,
|
||||
pollIntervalMs = 1000,
|
||||
keepaliveIntervalMs = 20000,
|
||||
}) {
|
||||
return async function getAgentRunEvents(request, response) {
|
||||
const userId = request.currentUser.id;
|
||||
const runId = request.params.runId;
|
||||
let closed = false;
|
||||
let lastPayload = null;
|
||||
let pollTimer = null;
|
||||
let keepaliveTimer = null;
|
||||
|
||||
const cleanup = () => {
|
||||
closed = true;
|
||||
if (pollTimer) clearInterval(pollTimer);
|
||||
if (keepaliveTimer) clearInterval(keepaliveTimer);
|
||||
pollTimer = null;
|
||||
keepaliveTimer = null;
|
||||
};
|
||||
|
||||
const sendEvent = (event, data) => {
|
||||
response.write(`event: ${event}\n`);
|
||||
response.write(`data: ${JSON.stringify(data)}\n\n`);
|
||||
};
|
||||
|
||||
const publish = async (prefetchedRun = null) => {
|
||||
if (closed) return;
|
||||
try {
|
||||
const run = prefetchedRun ?? await agentRunGateway.getRunForUser(userId, runId);
|
||||
if (!run) {
|
||||
sendEvent('error', { message: '任务不存在' });
|
||||
cleanup();
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
const nextPayload = JSON.stringify(run);
|
||||
if (nextPayload !== lastPayload) {
|
||||
lastPayload = nextPayload;
|
||||
sendEvent('run', { run });
|
||||
}
|
||||
if (run.status !== 'succeeded' && run.status !== 'failed') {
|
||||
agentRunGateway.dispatchRun(run.id);
|
||||
return;
|
||||
}
|
||||
cleanup();
|
||||
response.end();
|
||||
} catch (err) {
|
||||
sendEvent('error', {
|
||||
message: err instanceof Error ? err.message : '读取任务失败',
|
||||
});
|
||||
cleanup();
|
||||
response.end();
|
||||
}
|
||||
};
|
||||
|
||||
const firstRun = await agentRunGateway.getRunForUser(userId, runId);
|
||||
if (!firstRun) {
|
||||
response.status(404).json({ message: '任务不存在' });
|
||||
return;
|
||||
}
|
||||
|
||||
response.status(200);
|
||||
response.setHeader('Content-Type', 'text/event-stream');
|
||||
response.setHeader('Cache-Control', 'no-cache');
|
||||
response.setHeader('Connection', 'keep-alive');
|
||||
|
||||
request.on('close', cleanup);
|
||||
keepaliveTimer = setInterval(() => {
|
||||
if (!closed) response.write(': keepalive\n\n');
|
||||
}, keepaliveIntervalMs);
|
||||
pollTimer = setInterval(() => {
|
||||
void publish();
|
||||
}, pollIntervalMs);
|
||||
await publish(firstRun);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user