Merge branch 'worktree-task-cancellation': task cancellation feature
Tests / Test passed: 926

# Conflicts:
#	packages/cloud-platform/cloud/schema.py
This commit is contained in:
2026-07-15 19:39:28 +08:00
49 changed files with 2374 additions and 63 deletions
+36 -2
View File
@@ -11,6 +11,7 @@ authentication can be added later without changing route signatures.
from __future__ import annotations
import json
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Callable, Literal
from cloud.auth import (
@@ -31,6 +32,7 @@ from cloud.sdk.models import (
PluginRegistrationRequest,
PluginResponse,
TaskAttemptResponse,
TaskCancellationResponse,
TaskListItem,
TaskListResponse,
TaskPlannerDecisionItem,
@@ -39,7 +41,7 @@ from cloud.sdk.models import (
TaskSubmissionRequest,
TaskSubmissionResponse,
)
from fastapi import APIRouter, HTTPException, Query, Request, status
from fastapi import APIRouter, HTTPException, Query, Request, Response, status
if TYPE_CHECKING:
from cloud.plugins import PluginRegistry
@@ -156,7 +158,9 @@ def create_cloud_router(
@router.get("/tasks", response_model=TaskListResponse)
def list_tasks(
request: Request,
status_filter: Literal["queued", "assigned", "dispatched", "done", "failed"]
status_filter: Literal[
"queued", "assigned", "dispatched", "done", "failed", "cancelled"
]
| None = Query(default=None, alias="status"),
limit: int = Query(default=50, ge=1, le=100),
offset: int = Query(default=0, ge=0),
@@ -272,6 +276,36 @@ def create_cloud_router(
)
return TaskPlannerDecisionListResponse(items=items)
@router.post(
"/tasks/{task_id}/cancel",
response_model=TaskCancellationResponse,
responses={
status.HTTP_202_ACCEPTED: {"model": TaskCancellationResponse},
},
)
def cancel_task(
task_id: str, request: Request, response: Response
) -> TaskCancellationResponse:
_authorize(request, TASKS_SUBMIT_SCOPE)
result = scheduler.store.request_task_cancellation(
task_id, requested_at=datetime.now(UTC)
)
if result == "not_found":
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"task {task_id!r} not found",
)
if result == "already_terminal":
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"task {task_id!r} has already reached a terminal state",
)
task = scheduler.store.get_task(task_id)
assert task is not None
if result == "requested" and task.status != "cancelled":
response.status_code = status.HTTP_202_ACCEPTED
return TaskCancellationResponse(task_id=task_id, status=task.status)
@router.get("/devices", response_model=list[DeviceResponse])
def list_devices(request: Request) -> list[DeviceResponse]:
_authorize(request, POOL_READ_SCOPE)