From 730aa406ef3fcdbe3bdbff2d745db8a033cd7caf Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 30 May 2026 02:52:17 +0200 Subject: [PATCH] fix(ui): abort orphaned workboard runs --- ui/src/ui/controllers/workboard.test.ts | 11 +++++ ui/src/ui/controllers/workboard.ts | 58 ++++++++++++++++++------- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/ui/src/ui/controllers/workboard.test.ts b/ui/src/ui/controllers/workboard.test.ts index 221a855a4ce..fc14a0c30a9 100644 --- a/ui/src/ui/controllers/workboard.test.ts +++ b/ui/src/ui/controllers/workboard.test.ts @@ -598,6 +598,9 @@ describe("workboard controller", () => { if (method === "sessions.create") { return { key: "agent:main:dashboard:1", runId: "run-1" }; } + if (method === "chat.abort") { + return { aborted: true, runIds: ["run-1"] }; + } return {}; }); @@ -610,6 +613,14 @@ describe("workboard controller", () => { expect(sessionKey).toBeNull(); expect(client.request).toHaveBeenNthCalledWith( 4, + "chat.abort", + { + sessionKey: "agent:main:dashboard:1", + runId: "run-1", + }, + ); + expect(client.request).toHaveBeenNthCalledWith( + 5, "workboard.cards.update", expect.objectContaining({ patch: expect.objectContaining({ diff --git a/ui/src/ui/controllers/workboard.ts b/ui/src/ui/controllers/workboard.ts index bb8d7040f64..ed5d0245f62 100644 --- a/ui/src/ui/controllers/workboard.ts +++ b/ui/src/ui/controllers/workboard.ts @@ -1449,6 +1449,31 @@ function buildWorkboardExecution(params: { }; } +async function abortWorkboardSessionRun(params: { + client: GatewayBrowserClient; + sessionKey: string; + runId?: string; +}): Promise { + let abortResult = await params.client.request("chat.abort", { + sessionKey: params.sessionKey, + ...(params.runId ? { runId: params.runId } : {}), + }); + let aborted = + isRecord(abortResult) && + (abortResult.aborted === true || + (Array.isArray(abortResult.runIds) && abortResult.runIds.length > 0)); + if (!aborted && params.runId) { + abortResult = await params.client.request("chat.abort", { + sessionKey: params.sessionKey, + }); + aborted = + isRecord(abortResult) && + (abortResult.aborted === true || + (Array.isArray(abortResult.runIds) && abortResult.runIds.length > 0)); + } + return aborted; +} + export async function startWorkboardCard(params: { host: WorkboardHost; client: GatewayBrowserClient | null; @@ -1472,6 +1497,8 @@ export async function startWorkboardCard(params: { state.busyCardId = params.card.id; params.requestUpdate?.(); let preflightCard: WorkboardCard | null = null; + let createdSessionKey: string | null = null; + let createdRunId: string | undefined; try { const shouldClearManualSchedule = mode === "manual" && params.card.metadata?.automation?.scheduledAt !== undefined; @@ -1505,6 +1532,8 @@ export async function startWorkboardCard(params: { isRecord(created) && typeof created.runId === "string" && created.runId.trim() ? created.runId.trim() : undefined; + createdSessionKey = sessionKey; + createdRunId = runId; const initialRunFailed = mode === "autonomous" && isRecord(created) && created.runStarted === false; if (initialRunFailed) { @@ -1559,6 +1588,17 @@ export async function startWorkboardCard(params: { replaceCard(state, normalizeCardPayload(payload)); return sessionKey; } catch (error) { + if (mode === "autonomous" && createdSessionKey) { + try { + await abortWorkboardSessionRun({ + client: params.client, + sessionKey: createdSessionKey, + runId: createdRunId, + }); + } catch { + // Preserve the card-start failure; the user-facing repair is the rollback below. + } + } if (preflightCard) { try { const rollbackPayload = await params.client.request("workboard.cards.update", { @@ -1598,23 +1638,11 @@ export async function stopWorkboardCard(params: { state.error = null; params.requestUpdate?.(); try { - let abortResult = await params.client.request("chat.abort", { + const aborted = await abortWorkboardSessionRun({ + client: params.client, sessionKey, - ...(workboardCardRunId(params.card) ? { runId: workboardCardRunId(params.card) } : {}), + runId: workboardCardRunId(params.card), }); - let aborted = - isRecord(abortResult) && - (abortResult.aborted === true || - (Array.isArray(abortResult.runIds) && abortResult.runIds.length > 0)); - if (!aborted && workboardCardRunId(params.card)) { - abortResult = await params.client.request("chat.abort", { - sessionKey, - }); - aborted = - isRecord(abortResult) && - (abortResult.aborted === true || - (Array.isArray(abortResult.runIds) && abortResult.runIds.length > 0)); - } if (!aborted) { return; }