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:
@@ -2217,6 +2217,79 @@ type SubscribeOptions = {
|
||||
onBalance?: (update: BalanceUpdate) => void;
|
||||
};
|
||||
|
||||
export function subscribeAgentRunEvents(
|
||||
runId: string,
|
||||
onRun: (run: AgentRun) => void,
|
||||
onError: (error: Error) => void,
|
||||
): () => void {
|
||||
let closed = false;
|
||||
let activeController: AbortController | null = null;
|
||||
|
||||
const run = async (controller: AbortController) => {
|
||||
while (!closed && !controller.signal.aborted) {
|
||||
try {
|
||||
const res = await fetch(`${API}${AGENT_RUNS_PATH}/${encodeURIComponent(runId)}/events`, {
|
||||
headers: { Accept: 'text/event-stream' },
|
||||
credentials: 'same-origin',
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (res.status === 401) {
|
||||
notifyUnauthorized();
|
||||
throw new ApiError(401, 'SSE 未授权');
|
||||
}
|
||||
|
||||
if (!res.ok || !res.body) {
|
||||
throw new ApiError(res.status, `SSE failed: ${res.status}`);
|
||||
}
|
||||
|
||||
const reader = res.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
while (!closed && !controller.signal.aborted) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) return;
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const chunks = buffer.split('\n\n');
|
||||
buffer = chunks.pop() ?? '';
|
||||
|
||||
for (const chunk of chunks) {
|
||||
let eventName = 'message';
|
||||
let data = '';
|
||||
for (const line of chunk.split('\n')) {
|
||||
if (line.startsWith('event:')) eventName = line.slice(6).trim();
|
||||
if (line.startsWith('data:')) data = line.slice(5).trim();
|
||||
}
|
||||
if (!data) continue;
|
||||
if (eventName === 'run') {
|
||||
const payload = JSON.parse(data) as { run: AgentRun };
|
||||
onRun(payload.run);
|
||||
continue;
|
||||
}
|
||||
if (eventName === 'error') {
|
||||
const payload = JSON.parse(data) as { message?: string };
|
||||
throw new Error(payload.message || '后台任务失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (controller.signal.aborted || closed) return;
|
||||
onError(err instanceof Error ? err : new Error(String(err)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
activeController = new AbortController();
|
||||
void run(activeController);
|
||||
|
||||
return () => {
|
||||
closed = true;
|
||||
activeController?.abort();
|
||||
};
|
||||
}
|
||||
|
||||
export function subscribeSessionEvents(
|
||||
sessionId: string,
|
||||
onEvent: (event: SessionEvent) => void,
|
||||
|
||||
Reference in New Issue
Block a user