feat(h5-session): Session Broker、run SSE replay 与 Finish 竞态修复
落地 H5 Session 架构 Patch 1–5(Broker 收口、Router decision、SSE taxonomy、goosed 边界检查), 并新增可选 MEMIND_RUN_STREAM_REPLAY run 事件回放与 H5 假交付 guard;修复 Finish 先于 agent-run gate 导致 UI 永久 loading 的竞态,接入 verify:h5-session-patches 回归脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
export const RUN_STREAM_STATUS_EVENTS = new Set([
|
||||
'queued',
|
||||
'running',
|
||||
'retryable',
|
||||
'succeeded',
|
||||
'failed',
|
||||
]);
|
||||
|
||||
export const RUN_STREAM_TERMINAL_STATUSES = new Set(['succeeded', 'failed']);
|
||||
|
||||
function envFlag(value, fallback = false) {
|
||||
const raw = String(value ?? '').trim().toLowerCase();
|
||||
if (!raw) return fallback;
|
||||
return ['1', 'true', 'yes', 'on'].includes(raw);
|
||||
}
|
||||
|
||||
export function isRunStreamReplayEnabled(env = process.env) {
|
||||
return envFlag(env.MEMIND_RUN_STREAM_REPLAY, false);
|
||||
}
|
||||
|
||||
export function parseRunStreamLastEventId(value) {
|
||||
const id = String(value ?? '').trim();
|
||||
return id || null;
|
||||
}
|
||||
|
||||
export function extractRunFromStreamEvent(event) {
|
||||
if (!event) return null;
|
||||
if (event.eventType === 'run_snapshot' && event.data?.run) {
|
||||
return event.data.run;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function shouldEmitRunUpdateForStreamEvent(eventType) {
|
||||
return RUN_STREAM_STATUS_EVENTS.has(String(eventType ?? '').trim())
|
||||
|| String(eventType ?? '').trim() === 'run_snapshot';
|
||||
}
|
||||
|
||||
export function formatRunStreamSseChunk({ id = null, event, data }) {
|
||||
const lines = [];
|
||||
if (id) lines.push(`id: ${id}`);
|
||||
lines.push(`event: ${event}`);
|
||||
lines.push(`data: ${JSON.stringify(data)}`);
|
||||
return `${lines.join('\n')}\n\n`;
|
||||
}
|
||||
|
||||
export function projectRunStreamEvents(events, { fallbackRun = null } = {}) {
|
||||
let latestRun = fallbackRun;
|
||||
const emittedRuns = [];
|
||||
for (const event of Array.isArray(events) ? events : []) {
|
||||
const snapshotRun = extractRunFromStreamEvent(event);
|
||||
if (snapshotRun) {
|
||||
latestRun = snapshotRun;
|
||||
emittedRuns.push({ eventId: event.id, run: snapshotRun });
|
||||
continue;
|
||||
}
|
||||
if (shouldEmitRunUpdateForStreamEvent(event.eventType) && latestRun) {
|
||||
emittedRuns.push({ eventId: event.id, run: latestRun });
|
||||
}
|
||||
}
|
||||
return { latestRun, emittedRuns };
|
||||
}
|
||||
|
||||
export function isTerminalRunStatus(status) {
|
||||
return RUN_STREAM_TERMINAL_STATUSES.has(String(status ?? '').trim());
|
||||
}
|
||||
Reference in New Issue
Block a user