Allow agent to run in background after browser refresh

This commit is contained in:
2026-06-15 12:47:32 -07:00
parent 041aef63e2
commit 962114d1d3

View File

@@ -905,13 +905,11 @@ export async function POST(request: Request) {
process.env.VERCEL_URL ||
"https://vibnai.com";
// Honor client-side abort (Stop button). When the user clicks Stop
// the browser's AbortController fires `request.signal.aborted` and
// the fetch stream is closed; we use it as a polite checkpoint
// between rounds and tool calls so we (a) don't keep paying Gemini
// for tokens the user no longer wants and (b) persist whatever the
// assistant produced before the cancel.
const clientSignal = request.signal;
// We no longer honor client-side abort (Stop button / refresh) to cancel the AI.
// We use a dummy AbortController so that the backend can still be cancelled internally if needed,
// but the browser refresh / disconnect will not stop the agent's work.
const internalAbortController = new AbortController();
const clientSignal = internalAbortController.signal;
// Stream response
const encoder = new TextEncoder();
@@ -1188,11 +1186,18 @@ export async function POST(request: Request) {
const flushTimeline = () => {
if (!currentTimelineKind) return;
if (currentTimelineKind === "thought") {
assistantTimeline.push({ kind: "thought", text: currentTimelineText });
assistantTimeline.push({
kind: "thought",
text: currentTimelineText,
});
} else if (currentTimelineKind === "text") {
assistantText += (assistantText ? "\n\n" : "") + currentTimelineText;
assistantText +=
(assistantText ? "\n\n" : "") + currentTimelineText;
assistantTextSegments.push(currentTimelineText);
assistantTimeline.push({ kind: "text", text: currentTimelineText });
assistantTimeline.push({
kind: "text",
text: currentTimelineText,
});
}
currentTimelineKind = null;
currentTimelineText = "";
@@ -1200,7 +1205,7 @@ export async function POST(request: Request) {
for await (const chunk of stream) {
if (aborted) break;
if (chunk.type === "thinking_delta" && chunk.text) {
if (currentTimelineKind !== "thought") {
flushTimeline();
@@ -1947,9 +1952,11 @@ export async function POST(request: Request) {
}
},
cancel() {
// Browser disconnected (tab closed, navigated away). Clear the
// heartbeat so we stop writing to a closed stream.
// The abort handler above already flipped the flag so the loop bails.
// Browser disconnected (tab closed, navigated away, refreshed).
// We do NOT call internalAbortController.abort() here because we want
// the agent to continue its work in the background!
// We just clear the heartbeat to stop writing to the closed stream.
clearInterval(heartbeat);
},
});