feat: add turn cancellation UI and main process wiring

Adds cancel-turn IPC channel, SidecarClient.cancelTurn(), TurnCancelledError,
EryxAppService.cancelSessionTurn(), finalizeCancelledTurn(), stop button in
ChatPane, and cancelled run status throughout the run timeline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-25 22:17:15 +01:00
co-authored by Copilot
parent d84b3021f2
commit 82fdadb312
13 changed files with 265 additions and 49 deletions
+21 -2
View File
@@ -7,7 +7,7 @@ import type { PatternDefinition, ReasoningEffort } from '@shared/domain/pattern'
import type { ProjectRecord } from '@shared/domain/project';
import { createId } from '@shared/utils/ids';
export type SessionRunStatus = 'running' | 'completed' | 'error';
export type SessionRunStatus = 'running' | 'completed' | 'cancelled' | 'error';
export type SessionRunWorkspaceKind = 'project' | 'scratchpad';
export type RunTimelineEventKind =
| 'run-started'
@@ -17,6 +17,7 @@ export type RunTimelineEventKind =
| 'approval'
| 'message'
| 'run-completed'
| 'run-cancelled'
| 'run-failed';
export type RunTimelineEventStatus = 'running' | 'completed' | 'error';
@@ -318,7 +319,7 @@ export function normalizeSessionRunRecords(
triggerMessageId,
startedAt,
completedAt: normalizeOptionalString(run.completedAt),
status: run.status === 'error' ? 'error' : run.status === 'running' ? 'running' : 'completed',
status: run.status === 'error' ? 'error' : run.status === 'running' ? 'running' : run.status === 'cancelled' ? 'cancelled' : 'completed',
agents: run.agents.flatMap((agent) => {
const normalized = normalizeRunTimelineAgent(agent);
return normalized ? [normalized] : [];
@@ -516,6 +517,24 @@ export function completeSessionRunRecord(
});
}
export function cancelSessionRunRecord(
run: SessionRunRecord,
cancelledAt: string,
): SessionRunRecord {
const settledRun = settleOpenMessageEvents(run, 'completed', cancelledAt);
const cancelledRun: SessionRunRecord = {
...settledRun,
status: 'cancelled',
completedAt: cancelledAt,
};
return appendRunTimelineEvent(cancelledRun, {
kind: 'run-cancelled',
occurredAt: cancelledAt,
status: 'completed',
});
}
export function failSessionRunRecord(
run: SessionRunRecord,
failedAt: string,