Add page data delivery and publication guards

This commit is contained in:
john
2026-07-08 21:10:47 +08:00
parent 8d629e7a4e
commit eea14c1855
66 changed files with 3035 additions and 413 deletions
+62 -39
View File
@@ -112,49 +112,63 @@ function buildTempUser() {
};
}
async function waitForSessionFinish(fetchImpl, baseUrl, sessionId, cookie, timeoutMs, runTrigger) {
const response = await fetchImpl(`${baseUrl}/api/sessions/${encodeURIComponent(sessionId)}/events`, {
async function waitForAgentRunCompletion(fetchImpl, baseUrl, cookie, timeoutMs, createRun) {
const { runId, sessionId } = await createRun();
const deadline = Date.now() + timeoutMs;
const seen = [];
const response = await fetchImpl(`${baseUrl}/api/agent/runs/${encodeURIComponent(runId)}/events`, {
headers: {
Accept: 'text/event-stream',
Cookie: cookie,
},
});
if (!response.ok || !response.body) {
const payload = await parseResponseBody(response);
throw new Error(`session events failed: ${response.status} ${payload.text}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
const seen = [];
const runId = await runTrigger();
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const chunks = buffer.split('\n\n');
buffer = chunks.pop() ?? '';
for (const chunk of chunks) {
const trimmed = chunk.trim();
if (!trimmed) continue;
seen.push(trimmed);
if (trimmed.includes('type":"Error"')) {
throw new Error(`session stream error: ${trimmed}`);
}
if (trimmed.includes('type":"Finish"')) {
await reader.cancel().catch(() => {});
return { runId, seen };
if (response.ok && response.body) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (Date.now() < deadline) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const chunks = buffer.split('\n\n');
buffer = chunks.pop() ?? '';
for (const chunk of chunks) {
const trimmed = chunk.trim();
if (!trimmed || trimmed.startsWith(':')) continue;
seen.push(trimmed);
if (trimmed.includes('"status":"failed"')) {
await reader.cancel().catch(() => {});
return { runId, sessionId, seen, terminalStatus: 'failed' };
}
if (trimmed.includes('"status":"succeeded"')) {
await reader.cancel().catch(() => {});
return { runId, sessionId, seen, terminalStatus: 'succeeded' };
}
}
}
await reader.cancel().catch(() => {});
}
await reader.cancel().catch(() => {});
throw new Error(`session stream timeout after ${timeoutMs}ms`);
while (Date.now() < deadline) {
const run = await requestJson(fetchImpl, `${baseUrl}/api/agent/runs/${encodeURIComponent(runId)}`, {
headers: { Cookie: cookie },
timeoutMs: Math.min(10000, timeoutMs),
});
const status = run.json?.run?.status ?? null;
const resolvedSessionId = run.json?.run?.agent_session_id ?? sessionId;
if (status === 'succeeded' || status === 'failed') {
return { runId, sessionId: resolvedSessionId, seen, terminalStatus: status };
}
await new Promise((resolve) => setTimeout(resolve, 1500));
}
throw new Error(`agent run timeout after ${timeoutMs}ms`);
}
export { waitForAgentRunCompletion };
export async function runMemoryV2SessionFlowCli({
argv = process.argv.slice(2),
env = process.env,
@@ -219,10 +233,9 @@ export async function runMemoryV2SessionFlowCli({
}
const requestId = crypto.randomUUID();
const { runId, seen } = await waitForSessionFinish(
const { runId, sessionId: activeSessionId, seen, terminalStatus } = await waitForAgentRunCompletion(
fetchImpl,
options.baseUrl,
sessionId,
cookie,
options.timeoutMs,
async () => {
@@ -248,14 +261,18 @@ export async function runMemoryV2SessionFlowCli({
if (!created.ok || created.status !== 202 || !created.json?.run?.id) {
throw new Error(`agent run failed: ${created.status} ${created.text}`);
}
return created.json.run.id;
return {
runId: created.json.run.id,
sessionId: created.json?.run?.agent_session_id ?? sessionId,
};
},
);
checks.push(makeCheck('session_finish_seen', seen.some((chunk) => chunk.includes('type":"Finish"')), {
checks.push(makeCheck('agent_run_terminal', terminalStatus === 'succeeded', {
terminalStatus,
eventCount: seen.length,
}));
const detail = await requestJson(fetchImpl, `${options.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}`, {
const detail = await requestJson(fetchImpl, `${options.baseUrl}/api/sessions/${encodeURIComponent(activeSessionId)}`, {
headers: { Cookie: cookie },
timeoutMs: options.timeoutMs,
});
@@ -272,17 +289,22 @@ export async function runMemoryV2SessionFlowCli({
const remember = await requestJson(fetchImpl, `${options.baseUrl}/api/user-memory/v1/remember-recent`, {
method: 'POST',
headers: { Cookie: cookie },
body: { sessionId },
body: { sessionId: activeSessionId },
timeoutMs: options.timeoutMs,
});
checks.push(makeCheck('remember_recent_ok', remember.ok && remember.json?.ok === true, {
status: remember.status,
}));
checks.push(makeCheck('remember_recent_extracted', remember.ok && Number(remember.json?.analyzed ?? 0) > 0, {
status: remember.status,
analyzed: remember.json?.analyzed ?? 0,
memories: remember.json?.memories ?? 0,
}));
const sync = await requestJson(fetchImpl, `${options.baseUrl}/api/user-memory/v1/sync`, {
method: 'POST',
headers: { Cookie: cookie },
body: { sessionId },
body: { sessionId: activeSessionId },
timeoutMs: options.timeoutMs,
});
checks.push(makeCheck('sync_ok', sync.ok && sync.json?.ok === true, {
@@ -310,7 +332,7 @@ export async function runMemoryV2SessionFlowCli({
ok: checks.every((item) => item.ok),
baseUrl: options.baseUrl,
user: user.username,
sessionId,
sessionId: activeSessionId,
runId,
summary: {
assistantPreview: assistantMessages.at(-1)?.content?.[0]?.text?.slice?.(0, 200) ?? null,
@@ -326,6 +348,7 @@ export async function runMemoryV2SessionFlowCli({
}
: null,
sessionEventsTail: seen.slice(-4),
terminalStatus,
},
checks,
};