fix(ui): abort orphaned workboard runs

This commit is contained in:
Vincent Koc
2026-05-30 02:52:17 +02:00
parent 1a4eb0b5e7
commit 730aa406ef
2 changed files with 54 additions and 15 deletions

View File

@@ -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({

View File

@@ -1449,6 +1449,31 @@ function buildWorkboardExecution(params: {
};
}
async function abortWorkboardSessionRun(params: {
client: GatewayBrowserClient;
sessionKey: string;
runId?: string;
}): Promise<boolean> {
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;
}