diff --git a/frontend/src/adapters/mock/index.ts b/frontend/src/adapters/mock/index.ts index a520224..b03caa6 100644 --- a/frontend/src/adapters/mock/index.ts +++ b/frontend/src/adapters/mock/index.ts @@ -1701,6 +1701,14 @@ export class MockWorkStateGateway implements WorkStateGateway { this.states.get(projectId) ?? { agents: [], conversations: [] }, ); } + + async cancelBackgroundTask(_taskId: string): Promise { + // No-op in the mock; real refresh is driven by domain events. + } + + async retryBackgroundTask(_taskId: string): Promise { + // No-op in the mock; real refresh is driven by domain events. + } } /** diff --git a/frontend/src/adapters/workState.ts b/frontend/src/adapters/workState.ts index 7a5b0e1..9f523ca 100644 --- a/frontend/src/adapters/workState.ts +++ b/frontend/src/adapters/workState.ts @@ -16,4 +16,12 @@ export class TauriWorkStateGateway implements WorkStateGateway { const state = await invoke("get_project_work_state", { projectId }); return normalizeProjectWorkState(state); } + + async cancelBackgroundTask(taskId: string): Promise { + await invoke("cancel_background_task", { taskId }); + } + + async retryBackgroundTask(taskId: string): Promise { + await invoke("retry_background_task", { taskId }); + } } diff --git a/frontend/src/features/projects/ProjectsView.ls7.test.tsx b/frontend/src/features/projects/ProjectsView.ls7.test.tsx index 7c2db68..07695c2 100644 --- a/frontend/src/features/projects/ProjectsView.ls7.test.tsx +++ b/frontend/src/features/projects/ProjectsView.ls7.test.tsx @@ -63,7 +63,11 @@ function fixedWorkState(): WorkStateGateway { }, ], }; - return { getProjectWorkState: async () => structuredClone(state) }; + return { + getProjectWorkState: async () => structuredClone(state), + cancelBackgroundTask: async () => {}, + retryBackgroundTask: async () => {}, + }; } /** A conversation gateway that returns one identifiable turn for any thread. */ diff --git a/frontend/src/features/workstate/ProjectWorkStatePanel.tsx b/frontend/src/features/workstate/ProjectWorkStatePanel.tsx index ad52c12..85788f6 100644 --- a/frontend/src/features/workstate/ProjectWorkStatePanel.tsx +++ b/frontend/src/features/workstate/ProjectWorkStatePanel.tsx @@ -323,11 +323,36 @@ function InboxRow({ item }: { item: InboxItem }) { ); } -function BackgroundTaskRow({ task }: { task: BackgroundCompletion }) { +function BackgroundTaskRow({ + task, + onRefresh, +}: { + task: BackgroundCompletion; + onRefresh: () => Promise; +}) { + const { workState } = useGateways(); const [open, setOpen] = useState(false); + const [actionBusy, setActionBusy] = useState(false); + const [message, setMessage] = useState(null); const hasOutput = Boolean(task.stdoutTail || task.stderrTail); const canCancel = task.status === "running" || task.status === "pending"; const canRetry = task.status === "failed" || task.status === "cancelled"; + + async function runAction( + action: (taskId: string) => Promise, + ): Promise { + setActionBusy(true); + setMessage(null); + try { + await action(task.taskId); + await onRefresh(); + } catch (e) { + setMessage(e instanceof Error ? e.message : String(e)); + } finally { + setActionBusy(false); + } + } + return (
  • @@ -348,8 +373,11 @@ function BackgroundTaskRow({ task }: { task: BackgroundCompletion }) { @@ -364,8 +392,11 @@ function BackgroundTaskRow({ task }: { task: BackgroundCompletion }) { @@ -376,6 +407,11 @@ function BackgroundTaskRow({ task }: { task: BackgroundCompletion }) { {shortTicket(task.taskId)}
    + {message && ( +

    + {message} +

    + )} {open && hasOutput && (
                 {backgroundTasks.map((task) => (
    -              
    +              
                 ))}
               
             
    diff --git a/frontend/src/ports/index.ts b/frontend/src/ports/index.ts
    index d13068c..2335b7e 100644
    --- a/frontend/src/ports/index.ts
    +++ b/frontend/src/ports/index.ts
    @@ -666,6 +666,16 @@ export interface PermissionGateway {
     export interface WorkStateGateway {
       /** Reads the current per-agent live/offline and idle/busy state for a project. */
       getProjectWorkState(projectId: string): Promise;
    +  /**
    +   * Cancels a running/pending background task by id. The read-model refreshes
    +   * through the `backgroundTaskChanged` domain event.
    +   */
    +  cancelBackgroundTask(taskId: string): Promise;
    +  /**
    +   * Re-runs a failed/cancelled background task under a **new** task id (the
    +   * original id is never reused).
    +   */
    +  retryBackgroundTask(taskId: string): Promise;
     }
     
     /** One paginated page request over a conversation transcript (LS7). */