diff --git a/apps/x/apps/main/src/ipc.ts b/apps/x/apps/main/src/ipc.ts index 2896ee7a..eb0ea688 100644 --- a/apps/x/apps/main/src/ipc.ts +++ b/apps/x/apps/main/src/ipc.ts @@ -348,6 +348,10 @@ export function setupIpcHandlers() { 'runs:list': async (_event, args) => { return runsCore.listRuns(args.cursor); }, + 'runs:delete': async (_event, args) => { + await runsCore.deleteRun(args.runId); + return { success: true }; + }, 'models:list': async () => { return await listOnboardingModels(); }, diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index c177cf5f..6e951f34 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -2277,6 +2277,17 @@ function App() { onSelectRun: (runIdToLoad) => { void navigateToView({ type: 'chat', runId: runIdToLoad }) }, + onDeleteRun: async (runIdToDelete) => { + try { + await window.ipc.invoke('runs:delete', { runId: runIdToDelete }) + if (runId === runIdToDelete) { + void navigateToView({ type: 'chat', runId: null }) + } + await loadRuns() + } catch (err) { + console.error('Failed to delete run:', err) + } + }, onSelectBackgroundTask: (taskName) => { void navigateToView({ type: 'task', name: taskName }) }, diff --git a/apps/x/apps/renderer/src/components/sidebar-content.tsx b/apps/x/apps/renderer/src/components/sidebar-content.tsx index b2e02237..9377d82d 100644 --- a/apps/x/apps/renderer/src/components/sidebar-content.tsx +++ b/apps/x/apps/renderer/src/components/sidebar-content.tsx @@ -130,6 +130,7 @@ const SERVICE_LABELS: Record = { type TasksActions = { onNewChat: () => void onSelectRun: (runId: string) => void + onDeleteRun: (runId: string) => void onSelectBackgroundTask?: (taskName: string) => void } @@ -1056,7 +1057,7 @@ function TasksSection({ {runs.map((run) => ( - + actions?.onSelectRun(run.id)} @@ -1067,10 +1068,21 @@ function TasksSection({ ) : null} {run.title || '(Untitled chat)'} {run.createdAt ? ( - + {formatRunTime(run.createdAt)} ) : null} + diff --git a/apps/x/packages/core/src/runs/repo.ts b/apps/x/packages/core/src/runs/repo.ts index 3171d91e..15873c49 100644 --- a/apps/x/packages/core/src/runs/repo.ts +++ b/apps/x/packages/core/src/runs/repo.ts @@ -12,6 +12,7 @@ export interface IRunsRepo { fetch(id: string): Promise>; list(cursor?: string): Promise>; appendEvents(runId: string, events: z.infer[]): Promise; + delete(id: string): Promise; } /** @@ -236,4 +237,9 @@ export class FSRunsRepo implements IRunsRepo { ...(nextCursor ? { nextCursor } : {}), }; } + + async delete(id: string): Promise { + const filePath = path.join(WorkDir, 'runs', `${id}.jsonl`); + await fsp.unlink(filePath); + } } \ No newline at end of file diff --git a/apps/x/packages/core/src/runs/runs.ts b/apps/x/packages/core/src/runs/runs.ts index 80ffc80f..d15144c4 100644 --- a/apps/x/packages/core/src/runs/runs.ts +++ b/apps/x/packages/core/src/runs/runs.ts @@ -65,6 +65,11 @@ export async function stop(runId: string, force: boolean = false): Promise // This avoids duplicate events and ensures proper sequencing. } +export async function deleteRun(runId: string): Promise { + const repo = container.resolve('runsRepo'); + await repo.delete(runId); +} + export async function fetchRun(runId: string): Promise> { const repo = container.resolve('runsRepo'); return repo.fetch(runId); diff --git a/apps/x/packages/shared/src/ipc.ts b/apps/x/packages/shared/src/ipc.ts index 175c409e..aec5b293 100644 --- a/apps/x/packages/shared/src/ipc.ts +++ b/apps/x/packages/shared/src/ipc.ts @@ -173,6 +173,12 @@ const ipcSchemas = { }), res: ListRunsResponse, }, + 'runs:delete': { + req: z.object({ + runId: z.string(), + }), + res: z.object({ success: z.boolean() }), + }, 'runs:events': { req: z.null(), res: z.null(),